jquery_table_export_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2e4b008b90dabdceb789d156887784e3a8a262bd
4
+ data.tar.gz: 18e8e3eddebad6a9b4f30a6ea584bdad074fb05f
5
+ SHA512:
6
+ metadata.gz: 437d92a92dc4cd1be8765befdfd4746fb04c6b01e3f459270e50f7d3245ee58b37f449df52707c82ee0a9d8064bfa2c98411840413d6cd6e056a155d347e38bd
7
+ data.tar.gz: b7dea8c2137f030dd2e148e2afbe8811acf6506c70dcf0c9f7700271d466a85c18cca222b1fff6db84ff53d83b2ceeb40fb9ab8973c83249f276b31aa6358709
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jquery_table_export_rails.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Yusdirman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # JqueryTableExportRails
2
+
3
+ A ruby gem that uses the Rails asset pipeline to include the jQuery tableExport plugin by Giri Raj kayalshri (https://github.com/kayalshri/tableExport.jquery.plugin)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'jquery_table_export_rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install jquery_table_export_rails
20
+
21
+ ## Requirements
22
+
23
+ > NOTE: this is a jQuery plugin so you will also need the jquery-rails gem (it is not added by default starting from Rails 5.1):
24
+
25
+ * https://github.com/rails/jquery-rails
26
+
27
+ ## Usage
28
+
29
+ You will need to add this line into your application.js
30
+
31
+ ```
32
+ //= require jquery_table_export
33
+ ```
34
+
35
+ For further documentation, please refer to
36
+ * https://github.com/kayalshri/tableExport.jquery.plugin
37
+ * https://github.com/clarketm/TableExport
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it ( https://github.com/[my-github-username]/jquery_table_export_rails/fork )
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create a new Pull Request
46
+
47
+ ## Thanks
48
+
49
+ * Thanks to javascript developers
50
+ * Thanks to jquery developers
51
+ * Thanks to rubygem developers
52
+ * Thanks to rails developers
53
+ * Thanks to [Giri Raj](https://github.com/kayalshri) for the tableexport jquery plugin
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,3010 @@
1
+ /*
2
+ html2canvas 0.4.1 <http://html2canvas.hertzen.com>
3
+ Copyright (c) 2013 Niklas von Hertzen
4
+
5
+ Released under MIT License
6
+ */
7
+
8
+ (function(window, document, undefined){
9
+
10
+ //"use strict";
11
+
12
+ var _html2canvas = {},
13
+ previousElement,
14
+ computedCSS,
15
+ html2canvas;
16
+
17
+ _html2canvas.Util = {};
18
+
19
+ _html2canvas.Util.log = function(a) {
20
+ if (_html2canvas.logging && window.console && window.console.log) {
21
+ window.console.log(a);
22
+ }
23
+ };
24
+
25
+ _html2canvas.Util.trimText = (function(isNative){
26
+ return function(input) {
27
+ return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
28
+ };
29
+ })(String.prototype.trim);
30
+
31
+ _html2canvas.Util.asFloat = function(v) {
32
+ return parseFloat(v);
33
+ };
34
+
35
+ (function() {
36
+ // TODO: support all possible length values
37
+ var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
38
+ var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
39
+ _html2canvas.Util.parseTextShadows = function (value) {
40
+ if (!value || value === 'none') {
41
+ return [];
42
+ }
43
+
44
+ // find multiple shadow declarations
45
+ var shadows = value.match(TEXT_SHADOW_PROPERTY),
46
+ results = [];
47
+ for (var i = 0; shadows && (i < shadows.length); i++) {
48
+ var s = shadows[i].match(TEXT_SHADOW_VALUES);
49
+ results.push({
50
+ color: s[0],
51
+ offsetX: s[1] ? s[1].replace('px', '') : 0,
52
+ offsetY: s[2] ? s[2].replace('px', '') : 0,
53
+ blur: s[3] ? s[3].replace('px', '') : 0
54
+ });
55
+ }
56
+ return results;
57
+ };
58
+ })();
59
+
60
+ _html2canvas.Util.parseBackgroundImage = function (value) {
61
+ var whitespace = ' \r\n\t',
62
+ method, definition, prefix, prefix_i, block, results = [],
63
+ c, mode = 0, numParen = 0, quote, args;
64
+
65
+ var appendResult = function(){
66
+ if(method) {
67
+ if(definition.substr( 0, 1 ) === '"') {
68
+ definition = definition.substr( 1, definition.length - 2 );
69
+ }
70
+ if(definition) {
71
+ args.push(definition);
72
+ }
73
+ if(method.substr( 0, 1 ) === '-' &&
74
+ (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) {
75
+ prefix = method.substr( 0, prefix_i);
76
+ method = method.substr( prefix_i );
77
+ }
78
+ results.push({
79
+ prefix: prefix,
80
+ method: method.toLowerCase(),
81
+ value: block,
82
+ args: args
83
+ });
84
+ }
85
+ args = []; //for some odd reason, setting .length = 0 didn't work in safari
86
+ method =
87
+ prefix =
88
+ definition =
89
+ block = '';
90
+ };
91
+
92
+ appendResult();
93
+ for(var i = 0, ii = value.length; i<ii; i++) {
94
+ c = value[i];
95
+ if(mode === 0 && whitespace.indexOf( c ) > -1){
96
+ continue;
97
+ }
98
+ switch(c) {
99
+ case '"':
100
+ if(!quote) {
101
+ quote = c;
102
+ }
103
+ else if(quote === c) {
104
+ quote = null;
105
+ }
106
+ break;
107
+
108
+ case '(':
109
+ if(quote) { break; }
110
+ else if(mode === 0) {
111
+ mode = 1;
112
+ block += c;
113
+ continue;
114
+ } else {
115
+ numParen++;
116
+ }
117
+ break;
118
+
119
+ case ')':
120
+ if(quote) { break; }
121
+ else if(mode === 1) {
122
+ if(numParen === 0) {
123
+ mode = 0;
124
+ block += c;
125
+ appendResult();
126
+ continue;
127
+ } else {
128
+ numParen--;
129
+ }
130
+ }
131
+ break;
132
+
133
+ case ',':
134
+ if(quote) { break; }
135
+ else if(mode === 0) {
136
+ appendResult();
137
+ continue;
138
+ }
139
+ else if (mode === 1) {
140
+ if(numParen === 0 && !method.match(/^url$/i)) {
141
+ args.push(definition);
142
+ definition = '';
143
+ block += c;
144
+ continue;
145
+ }
146
+ }
147
+ break;
148
+ }
149
+
150
+ block += c;
151
+ if(mode === 0) { method += c; }
152
+ else { definition += c; }
153
+ }
154
+ appendResult();
155
+
156
+ return results;
157
+ };
158
+
159
+ _html2canvas.Util.Bounds = function (element) {
160
+ var clientRect, bounds = {};
161
+
162
+ if (element.getBoundingClientRect){
163
+ clientRect = element.getBoundingClientRect();
164
+
165
+ // TODO add scroll position to bounds, so no scrolling of window necessary
166
+ bounds.top = clientRect.top;
167
+ bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
168
+ bounds.left = clientRect.left;
169
+
170
+ bounds.width = element.offsetWidth;
171
+ bounds.height = element.offsetHeight;
172
+ }
173
+
174
+ return bounds;
175
+ };
176
+
177
+ // TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
178
+ // but would require further work to calculate the correct positions for elements with offsetParents
179
+ _html2canvas.Util.OffsetBounds = function (element) {
180
+ var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
181
+
182
+ return {
183
+ top: element.offsetTop + parent.top,
184
+ bottom: element.offsetTop + element.offsetHeight + parent.top,
185
+ left: element.offsetLeft + parent.left,
186
+ width: element.offsetWidth,
187
+ height: element.offsetHeight
188
+ };
189
+ };
190
+
191
+ function toPX(element, attribute, value ) {
192
+ var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
193
+ left,
194
+ style = element.style;
195
+
196
+ // Check if we are not dealing with pixels, (Opera has issues with this)
197
+ // Ported from jQuery css.js
198
+ // From the awesome hack by Dean Edwards
199
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
200
+
201
+ // If we're not dealing with a regular pixel number
202
+ // but a number that has a weird ending, we need to convert it to pixels
203
+
204
+ if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) {
205
+ // Remember the original values
206
+ left = style.left;
207
+
208
+ // Put in the new values to get a computed value out
209
+ if (rsLeft) {
210
+ element.runtimeStyle.left = element.currentStyle.left;
211
+ }
212
+ style.left = attribute === "fontSize" ? "1em" : (value || 0);
213
+ value = style.pixelLeft + "px";
214
+
215
+ // Revert the changed values
216
+ style.left = left;
217
+ if (rsLeft) {
218
+ element.runtimeStyle.left = rsLeft;
219
+ }
220
+ }
221
+
222
+ if (!/^(thin|medium|thick)$/i.test(value)) {
223
+ return Math.round(parseFloat(value)) + "px";
224
+ }
225
+
226
+ return value;
227
+ }
228
+
229
+ function asInt(val) {
230
+ return parseInt(val, 10);
231
+ }
232
+
233
+ function isPercentage(value) {
234
+ return value.toString().indexOf("%") !== -1;
235
+ }
236
+
237
+ function parseBackgroundSizePosition(value, element, attribute, index) {
238
+ value = (value || '').split(',');
239
+ value = value[index || 0] || value[0] || 'auto';
240
+ value = _html2canvas.Util.trimText(value).split(' ');
241
+ if(attribute === 'backgroundSize' && (value[0] && value[0].match(/^(cover|contain|auto)$/))) {
242
+ return value;
243
+ } else {
244
+ value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
245
+ if(value[1] === undefined) {
246
+ if(attribute === 'backgroundSize') {
247
+ value[1] = 'auto';
248
+ return value;
249
+ } else {
250
+ // IE 9 doesn't return double digit always
251
+ value[1] = value[0];
252
+ }
253
+ }
254
+ value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
255
+ }
256
+ return value;
257
+ }
258
+
259
+ _html2canvas.Util.getCSS = function (element, attribute, index) {
260
+ if (previousElement !== element) {
261
+ computedCSS = document.defaultView.getComputedStyle(element, null);
262
+ }
263
+
264
+ var value = computedCSS[attribute];
265
+
266
+ if (/^background(Size|Position)$/.test(attribute)) {
267
+ return parseBackgroundSizePosition(value, element, attribute, index);
268
+ } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
269
+ var arr = value.split(" ");
270
+ if (arr.length <= 1) {
271
+ arr[1] = arr[0];
272
+ }
273
+ return arr.map(asInt);
274
+ }
275
+
276
+ return value;
277
+ };
278
+
279
+ _html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
280
+ var target_ratio = target_width / target_height,
281
+ current_ratio = current_width / current_height,
282
+ output_width, output_height;
283
+
284
+ if(!stretch_mode || stretch_mode === 'auto') {
285
+ output_width = target_width;
286
+ output_height = target_height;
287
+ } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
288
+ output_height = target_height;
289
+ output_width = target_height * current_ratio;
290
+ } else {
291
+ output_width = target_width;
292
+ output_height = target_width / current_ratio;
293
+ }
294
+
295
+ return {
296
+ width: output_width,
297
+ height: output_height
298
+ };
299
+ };
300
+
301
+ _html2canvas.Util.BackgroundPosition = function(element, bounds, image, imageIndex, backgroundSize ) {
302
+ var backgroundPosition = _html2canvas.Util.getCSS(element, 'backgroundPosition', imageIndex),
303
+ leftPosition,
304
+ topPosition;
305
+ if (backgroundPosition.length === 1){
306
+ backgroundPosition = [backgroundPosition[0], backgroundPosition[0]];
307
+ }
308
+
309
+ if (isPercentage(backgroundPosition[0])){
310
+ leftPosition = (bounds.width - (backgroundSize || image).width) * (parseFloat(backgroundPosition[0]) / 100);
311
+ } else {
312
+ leftPosition = parseInt(backgroundPosition[0], 10);
313
+ }
314
+
315
+ if (backgroundPosition[1] === 'auto') {
316
+ topPosition = leftPosition / image.width * image.height;
317
+ } else if (isPercentage(backgroundPosition[1])){
318
+ topPosition = (bounds.height - (backgroundSize || image).height) * parseFloat(backgroundPosition[1]) / 100;
319
+ } else {
320
+ topPosition = parseInt(backgroundPosition[1], 10);
321
+ }
322
+
323
+ if (backgroundPosition[0] === 'auto') {
324
+ leftPosition = topPosition / image.height * image.width;
325
+ }
326
+
327
+ return {left: leftPosition, top: topPosition};
328
+ };
329
+
330
+ _html2canvas.Util.BackgroundSize = function(element, bounds, image, imageIndex) {
331
+ var backgroundSize = _html2canvas.Util.getCSS(element, 'backgroundSize', imageIndex), width, height;
332
+
333
+ if (backgroundSize.length === 1) {
334
+ backgroundSize = [backgroundSize[0], backgroundSize[0]];
335
+ }
336
+
337
+ if (isPercentage(backgroundSize[0])) {
338
+ width = bounds.width * parseFloat(backgroundSize[0]) / 100;
339
+ } else if (/contain|cover/.test(backgroundSize[0])) {
340
+ return _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, backgroundSize[0]);
341
+ } else {
342
+ width = parseInt(backgroundSize[0], 10);
343
+ }
344
+
345
+ if (backgroundSize[0] === 'auto' && backgroundSize[1] === 'auto') {
346
+ height = image.height;
347
+ } else if (backgroundSize[1] === 'auto') {
348
+ height = width / image.width * image.height;
349
+ } else if (isPercentage(backgroundSize[1])) {
350
+ height = bounds.height * parseFloat(backgroundSize[1]) / 100;
351
+ } else {
352
+ height = parseInt(backgroundSize[1], 10);
353
+ }
354
+
355
+ if (backgroundSize[0] === 'auto') {
356
+ width = height / image.height * image.width;
357
+ }
358
+
359
+ return {width: width, height: height};
360
+ };
361
+
362
+ _html2canvas.Util.BackgroundRepeat = function(element, imageIndex) {
363
+ var backgroundRepeat = _html2canvas.Util.getCSS(element, "backgroundRepeat").split(",").map(_html2canvas.Util.trimText);
364
+ return backgroundRepeat[imageIndex] || backgroundRepeat[0];
365
+ };
366
+
367
+ _html2canvas.Util.Extend = function (options, defaults) {
368
+ for (var key in options) {
369
+ if (options.hasOwnProperty(key)) {
370
+ defaults[key] = options[key];
371
+ }
372
+ }
373
+ return defaults;
374
+ };
375
+
376
+
377
+ /*
378
+ * Derived from jQuery.contents()
379
+ * Copyright 2010, John Resig
380
+ * Dual licensed under the MIT or GPL Version 2 licenses.
381
+ * http://jquery.org/license
382
+ */
383
+ _html2canvas.Util.Children = function( elem ) {
384
+ var children;
385
+ try {
386
+ children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
387
+ var ret = [];
388
+ if (array !== null) {
389
+ (function(first, second ) {
390
+ var i = first.length,
391
+ j = 0;
392
+
393
+ if (typeof second.length === "number") {
394
+ for (var l = second.length; j < l; j++) {
395
+ first[i++] = second[j];
396
+ }
397
+ } else {
398
+ while (second[j] !== undefined) {
399
+ first[i++] = second[j++];
400
+ }
401
+ }
402
+
403
+ first.length = i;
404
+
405
+ return first;
406
+ })(ret, array);
407
+ }
408
+ return ret;
409
+ })(elem.childNodes);
410
+
411
+ } catch (ex) {
412
+ _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
413
+ children = [];
414
+ }
415
+ return children;
416
+ };
417
+
418
+ _html2canvas.Util.isTransparent = function(backgroundColor) {
419
+ return (!backgroundColor || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
420
+ };
421
+
422
+ _html2canvas.Util.Font = (function () {
423
+
424
+ var fontData = {};
425
+
426
+ return function(font, fontSize, doc) {
427
+ if (fontData[font + "-" + fontSize] !== undefined) {
428
+ return fontData[font + "-" + fontSize];
429
+ }
430
+
431
+ var container = doc.createElement('div'),
432
+ img = doc.createElement('img'),
433
+ span = doc.createElement('span'),
434
+ sampleText = 'Hidden Text',
435
+ baseline,
436
+ middle,
437
+ metricsObj;
438
+
439
+ container.style.visibility = "hidden";
440
+ container.style.fontFamily = font;
441
+ container.style.fontSize = fontSize;
442
+ container.style.margin = 0;
443
+ container.style.padding = 0;
444
+
445
+ doc.body.appendChild(container);
446
+
447
+ // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
448
+ img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=";
449
+ img.width = 1;
450
+ img.height = 1;
451
+
452
+ img.style.margin = 0;
453
+ img.style.padding = 0;
454
+ img.style.verticalAlign = "baseline";
455
+
456
+ span.style.fontFamily = font;
457
+ span.style.fontSize = fontSize;
458
+ span.style.margin = 0;
459
+ span.style.padding = 0;
460
+
461
+ span.appendChild(doc.createTextNode(sampleText));
462
+ container.appendChild(span);
463
+ container.appendChild(img);
464
+ baseline = (img.offsetTop - span.offsetTop) + 1;
465
+
466
+ container.removeChild(span);
467
+ container.appendChild(doc.createTextNode(sampleText));
468
+
469
+ container.style.lineHeight = "normal";
470
+ img.style.verticalAlign = "super";
471
+
472
+ middle = (img.offsetTop-container.offsetTop) + 1;
473
+ metricsObj = {
474
+ baseline: baseline,
475
+ lineWidth: 1,
476
+ middle: middle
477
+ };
478
+
479
+ fontData[font + "-" + fontSize] = metricsObj;
480
+
481
+ doc.body.removeChild(container);
482
+
483
+ return metricsObj;
484
+ };
485
+ })();
486
+
487
+ (function(){
488
+ var Util = _html2canvas.Util,
489
+ Generate = {};
490
+
491
+ _html2canvas.Generate = Generate;
492
+
493
+ var reGradients = [
494
+ /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
495
+ /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
496
+ /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
497
+ /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
498
+ /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
499
+ /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
500
+ /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
501
+ ];
502
+
503
+ /*
504
+ * TODO: Add IE10 vendor prefix (-ms) support
505
+ * TODO: Add W3C gradient (linear-gradient) support
506
+ * TODO: Add old Webkit -webkit-gradient(radial, ...) support
507
+ * TODO: Maybe some RegExp optimizations are possible ;o)
508
+ */
509
+ Generate.parseGradient = function(css, bounds) {
510
+ var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl;
511
+
512
+ for(i = 0; i < len; i+=1){
513
+ m1 = css.match(reGradients[i]);
514
+ if(m1) {
515
+ break;
516
+ }
517
+ }
518
+
519
+ if(m1) {
520
+ switch(m1[1]) {
521
+ case '-webkit-linear-gradient':
522
+ case '-o-linear-gradient':
523
+
524
+ gradient = {
525
+ type: 'linear',
526
+ x0: null,
527
+ y0: null,
528
+ x1: null,
529
+ y1: null,
530
+ colorStops: []
531
+ };
532
+
533
+ // get coordinates
534
+ m2 = m1[2].match(/\w+/g);
535
+ if(m2){
536
+ m2Len = m2.length;
537
+ for(i = 0; i < m2Len; i+=1){
538
+ switch(m2[i]) {
539
+ case 'top':
540
+ gradient.y0 = 0;
541
+ gradient.y1 = bounds.height;
542
+ break;
543
+
544
+ case 'right':
545
+ gradient.x0 = bounds.width;
546
+ gradient.x1 = 0;
547
+ break;
548
+
549
+ case 'bottom':
550
+ gradient.y0 = bounds.height;
551
+ gradient.y1 = 0;
552
+ break;
553
+
554
+ case 'left':
555
+ gradient.x0 = 0;
556
+ gradient.x1 = bounds.width;
557
+ break;
558
+ }
559
+ }
560
+ }
561
+ if(gradient.x0 === null && gradient.x1 === null){ // center
562
+ gradient.x0 = gradient.x1 = bounds.width / 2;
563
+ }
564
+ if(gradient.y0 === null && gradient.y1 === null){ // center
565
+ gradient.y0 = gradient.y1 = bounds.height / 2;
566
+ }
567
+
568
+ // get colors and stops
569
+ m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
570
+ if(m2){
571
+ m2Len = m2.length;
572
+ step = 1 / Math.max(m2Len - 1, 1);
573
+ for(i = 0; i < m2Len; i+=1){
574
+ m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
575
+ if(m3[2]){
576
+ stop = parseFloat(m3[2]);
577
+ if(m3[3] === '%'){
578
+ stop /= 100;
579
+ } else { // px - stupid opera
580
+ stop /= bounds.width;
581
+ }
582
+ } else {
583
+ stop = i * step;
584
+ }
585
+ gradient.colorStops.push({
586
+ color: m3[1],
587
+ stop: stop
588
+ });
589
+ }
590
+ }
591
+ break;
592
+
593
+ case '-webkit-gradient':
594
+
595
+ gradient = {
596
+ type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
597
+ x0: 0,
598
+ y0: 0,
599
+ x1: 0,
600
+ y1: 0,
601
+ colorStops: []
602
+ };
603
+
604
+ // get coordinates
605
+ m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
606
+ if(m2){
607
+ gradient.x0 = (m2[1] * bounds.width) / 100;
608
+ gradient.y0 = (m2[2] * bounds.height) / 100;
609
+ gradient.x1 = (m2[3] * bounds.width) / 100;
610
+ gradient.y1 = (m2[4] * bounds.height) / 100;
611
+ }
612
+
613
+ // get colors and stops
614
+ m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g);
615
+ if(m2){
616
+ m2Len = m2.length;
617
+ for(i = 0; i < m2Len; i+=1){
618
+ m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/);
619
+ stop = parseFloat(m3[2]);
620
+ if(m3[1] === 'from') {
621
+ stop = 0.0;
622
+ }
623
+ if(m3[1] === 'to') {
624
+ stop = 1.0;
625
+ }
626
+ gradient.colorStops.push({
627
+ color: m3[3],
628
+ stop: stop
629
+ });
630
+ }
631
+ }
632
+ break;
633
+
634
+ case '-moz-linear-gradient':
635
+
636
+ gradient = {
637
+ type: 'linear',
638
+ x0: 0,
639
+ y0: 0,
640
+ x1: 0,
641
+ y1: 0,
642
+ colorStops: []
643
+ };
644
+
645
+ // get coordinates
646
+ m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
647
+
648
+ // m2[1] == 0% -> left
649
+ // m2[1] == 50% -> center
650
+ // m2[1] == 100% -> right
651
+
652
+ // m2[2] == 0% -> top
653
+ // m2[2] == 50% -> center
654
+ // m2[2] == 100% -> bottom
655
+
656
+ if(m2){
657
+ gradient.x0 = (m2[1] * bounds.width) / 100;
658
+ gradient.y0 = (m2[2] * bounds.height) / 100;
659
+ gradient.x1 = bounds.width - gradient.x0;
660
+ gradient.y1 = bounds.height - gradient.y0;
661
+ }
662
+
663
+ // get colors and stops
664
+ m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g);
665
+ if(m2){
666
+ m2Len = m2.length;
667
+ step = 1 / Math.max(m2Len - 1, 1);
668
+ for(i = 0; i < m2Len; i+=1){
669
+ m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
670
+ if(m3[2]){
671
+ stop = parseFloat(m3[2]);
672
+ if(m3[3]){ // percentage
673
+ stop /= 100;
674
+ }
675
+ } else {
676
+ stop = i * step;
677
+ }
678
+ gradient.colorStops.push({
679
+ color: m3[1],
680
+ stop: stop
681
+ });
682
+ }
683
+ }
684
+ break;
685
+
686
+ case '-webkit-radial-gradient':
687
+ case '-moz-radial-gradient':
688
+ case '-o-radial-gradient':
689
+
690
+ gradient = {
691
+ type: 'circle',
692
+ x0: 0,
693
+ y0: 0,
694
+ x1: bounds.width,
695
+ y1: bounds.height,
696
+ cx: 0,
697
+ cy: 0,
698
+ rx: 0,
699
+ ry: 0,
700
+ colorStops: []
701
+ };
702
+
703
+ // center
704
+ m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
705
+ if(m2){
706
+ gradient.cx = (m2[1] * bounds.width) / 100;
707
+ gradient.cy = (m2[2] * bounds.height) / 100;
708
+ }
709
+
710
+ // size
711
+ m2 = m1[3].match(/\w+/);
712
+ m3 = m1[4].match(/[a-z\-]*/);
713
+ if(m2 && m3){
714
+ switch(m3[0]){
715
+ case 'farthest-corner':
716
+ case 'cover': // is equivalent to farthest-corner
717
+ case '': // mozilla removes "cover" from definition :(
718
+ tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
719
+ tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
720
+ br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
721
+ bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
722
+ gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
723
+ break;
724
+ case 'closest-corner':
725
+ tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
726
+ tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
727
+ br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
728
+ bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
729
+ gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
730
+ break;
731
+ case 'farthest-side':
732
+ if(m2[0] === 'circle'){
733
+ gradient.rx = gradient.ry = Math.max(
734
+ gradient.cx,
735
+ gradient.cy,
736
+ gradient.x1 - gradient.cx,
737
+ gradient.y1 - gradient.cy
738
+ );
739
+ } else { // ellipse
740
+
741
+ gradient.type = m2[0];
742
+
743
+ gradient.rx = Math.max(
744
+ gradient.cx,
745
+ gradient.x1 - gradient.cx
746
+ );
747
+ gradient.ry = Math.max(
748
+ gradient.cy,
749
+ gradient.y1 - gradient.cy
750
+ );
751
+ }
752
+ break;
753
+ case 'closest-side':
754
+ case 'contain': // is equivalent to closest-side
755
+ if(m2[0] === 'circle'){
756
+ gradient.rx = gradient.ry = Math.min(
757
+ gradient.cx,
758
+ gradient.cy,
759
+ gradient.x1 - gradient.cx,
760
+ gradient.y1 - gradient.cy
761
+ );
762
+ } else { // ellipse
763
+
764
+ gradient.type = m2[0];
765
+
766
+ gradient.rx = Math.min(
767
+ gradient.cx,
768
+ gradient.x1 - gradient.cx
769
+ );
770
+ gradient.ry = Math.min(
771
+ gradient.cy,
772
+ gradient.y1 - gradient.cy
773
+ );
774
+ }
775
+ break;
776
+
777
+ // TODO: add support for "30px 40px" sizes (webkit only)
778
+ }
779
+ }
780
+
781
+ // color stops
782
+ m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
783
+ if(m2){
784
+ m2Len = m2.length;
785
+ step = 1 / Math.max(m2Len - 1, 1);
786
+ for(i = 0; i < m2Len; i+=1){
787
+ m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
788
+ if(m3[2]){
789
+ stop = parseFloat(m3[2]);
790
+ if(m3[3] === '%'){
791
+ stop /= 100;
792
+ } else { // px - stupid opera
793
+ stop /= bounds.width;
794
+ }
795
+ } else {
796
+ stop = i * step;
797
+ }
798
+ gradient.colorStops.push({
799
+ color: m3[1],
800
+ stop: stop
801
+ });
802
+ }
803
+ }
804
+ break;
805
+ }
806
+ }
807
+
808
+ return gradient;
809
+ };
810
+
811
+ function addScrollStops(grad) {
812
+ return function(colorStop) {
813
+ try {
814
+ grad.addColorStop(colorStop.stop, colorStop.color);
815
+ }
816
+ catch(e) {
817
+ Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
818
+ }
819
+ };
820
+ }
821
+
822
+ Generate.Gradient = function(src, bounds) {
823
+ if(bounds.width === 0 || bounds.height === 0) {
824
+ return;
825
+ }
826
+
827
+ var canvas = document.createElement('canvas'),
828
+ ctx = canvas.getContext('2d'),
829
+ gradient, grad;
830
+
831
+ canvas.width = bounds.width;
832
+ canvas.height = bounds.height;
833
+
834
+ // TODO: add support for multi defined background gradients
835
+ gradient = _html2canvas.Generate.parseGradient(src, bounds);
836
+
837
+ if(gradient) {
838
+ switch(gradient.type) {
839
+ case 'linear':
840
+ grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
841
+ gradient.colorStops.forEach(addScrollStops(grad));
842
+ ctx.fillStyle = grad;
843
+ ctx.fillRect(0, 0, bounds.width, bounds.height);
844
+ break;
845
+
846
+ case 'circle':
847
+ grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
848
+ gradient.colorStops.forEach(addScrollStops(grad));
849
+ ctx.fillStyle = grad;
850
+ ctx.fillRect(0, 0, bounds.width, bounds.height);
851
+ break;
852
+
853
+ case 'ellipse':
854
+ var canvasRadial = document.createElement('canvas'),
855
+ ctxRadial = canvasRadial.getContext('2d'),
856
+ ri = Math.max(gradient.rx, gradient.ry),
857
+ di = ri * 2;
858
+
859
+ canvasRadial.width = canvasRadial.height = di;
860
+
861
+ grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
862
+ gradient.colorStops.forEach(addScrollStops(grad));
863
+
864
+ ctxRadial.fillStyle = grad;
865
+ ctxRadial.fillRect(0, 0, di, di);
866
+
867
+ ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
868
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
869
+ ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
870
+ break;
871
+ }
872
+ }
873
+
874
+ return canvas;
875
+ };
876
+
877
+ Generate.ListAlpha = function(number) {
878
+ var tmp = "",
879
+ modulus;
880
+
881
+ do {
882
+ modulus = number % 26;
883
+ tmp = String.fromCharCode((modulus) + 64) + tmp;
884
+ number = number / 26;
885
+ }while((number*26) > 26);
886
+
887
+ return tmp;
888
+ };
889
+
890
+ Generate.ListRoman = function(number) {
891
+ var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
892
+ decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
893
+ roman = "",
894
+ v,
895
+ len = romanArray.length;
896
+
897
+ if (number <= 0 || number >= 4000) {
898
+ return number;
899
+ }
900
+
901
+ for (v=0; v < len; v+=1) {
902
+ while (number >= decimal[v]) {
903
+ number -= decimal[v];
904
+ roman += romanArray[v];
905
+ }
906
+ }
907
+
908
+ return roman;
909
+ };
910
+ })();
911
+ function h2cRenderContext(width, height) {
912
+ var storage = [];
913
+ return {
914
+ storage: storage,
915
+ width: width,
916
+ height: height,
917
+ clip: function() {
918
+ storage.push({
919
+ type: "function",
920
+ name: "clip",
921
+ 'arguments': arguments
922
+ });
923
+ },
924
+ translate: function() {
925
+ storage.push({
926
+ type: "function",
927
+ name: "translate",
928
+ 'arguments': arguments
929
+ });
930
+ },
931
+ fill: function() {
932
+ storage.push({
933
+ type: "function",
934
+ name: "fill",
935
+ 'arguments': arguments
936
+ });
937
+ },
938
+ save: function() {
939
+ storage.push({
940
+ type: "function",
941
+ name: "save",
942
+ 'arguments': arguments
943
+ });
944
+ },
945
+ restore: function() {
946
+ storage.push({
947
+ type: "function",
948
+ name: "restore",
949
+ 'arguments': arguments
950
+ });
951
+ },
952
+ fillRect: function () {
953
+ storage.push({
954
+ type: "function",
955
+ name: "fillRect",
956
+ 'arguments': arguments
957
+ });
958
+ },
959
+ createPattern: function() {
960
+ storage.push({
961
+ type: "function",
962
+ name: "createPattern",
963
+ 'arguments': arguments
964
+ });
965
+ },
966
+ drawShape: function() {
967
+
968
+ var shape = [];
969
+
970
+ storage.push({
971
+ type: "function",
972
+ name: "drawShape",
973
+ 'arguments': shape
974
+ });
975
+
976
+ return {
977
+ moveTo: function() {
978
+ shape.push({
979
+ name: "moveTo",
980
+ 'arguments': arguments
981
+ });
982
+ },
983
+ lineTo: function() {
984
+ shape.push({
985
+ name: "lineTo",
986
+ 'arguments': arguments
987
+ });
988
+ },
989
+ arcTo: function() {
990
+ shape.push({
991
+ name: "arcTo",
992
+ 'arguments': arguments
993
+ });
994
+ },
995
+ bezierCurveTo: function() {
996
+ shape.push({
997
+ name: "bezierCurveTo",
998
+ 'arguments': arguments
999
+ });
1000
+ },
1001
+ quadraticCurveTo: function() {
1002
+ shape.push({
1003
+ name: "quadraticCurveTo",
1004
+ 'arguments': arguments
1005
+ });
1006
+ }
1007
+ };
1008
+
1009
+ },
1010
+ drawImage: function () {
1011
+ storage.push({
1012
+ type: "function",
1013
+ name: "drawImage",
1014
+ 'arguments': arguments
1015
+ });
1016
+ },
1017
+ fillText: function () {
1018
+ storage.push({
1019
+ type: "function",
1020
+ name: "fillText",
1021
+ 'arguments': arguments
1022
+ });
1023
+ },
1024
+ setVariable: function (variable, value) {
1025
+ storage.push({
1026
+ type: "variable",
1027
+ name: variable,
1028
+ 'arguments': value
1029
+ });
1030
+ return value;
1031
+ }
1032
+ };
1033
+ }
1034
+ _html2canvas.Parse = function (images, options, cb) {
1035
+ window.scroll(0,0);
1036
+
1037
+ var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
1038
+ numDraws = 0,
1039
+ doc = element.ownerDocument,
1040
+ Util = _html2canvas.Util,
1041
+ support = Util.Support(options, doc),
1042
+ ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
1043
+ body = doc.body,
1044
+ getCSS = Util.getCSS,
1045
+ pseudoHide = "___html2canvas___pseudoelement",
1046
+ hidePseudoElementsStyles = doc.createElement('style');
1047
+
1048
+ hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
1049
+ '-parent:before { content: "" !important; display: none !important; }' +
1050
+ '.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
1051
+
1052
+ body.appendChild(hidePseudoElementsStyles);
1053
+
1054
+ images = images || {};
1055
+
1056
+ init();
1057
+
1058
+ function init() {
1059
+ var background = getCSS(document.documentElement, "backgroundColor"),
1060
+ transparentBackground = (Util.isTransparent(background) && element === document.body),
1061
+ stack = renderElement(element, null, false, transparentBackground);
1062
+
1063
+ // create pseudo elements in a single pass to prevent synchronous layouts
1064
+ addPseudoElements(element);
1065
+
1066
+ parseChildren(element, stack, function() {
1067
+ if (transparentBackground) {
1068
+ background = stack.backgroundColor;
1069
+ }
1070
+
1071
+ removePseudoElements();
1072
+
1073
+ Util.log('Done parsing, moving to Render.');
1074
+
1075
+ cb({
1076
+ backgroundColor: background,
1077
+ stack: stack
1078
+ });
1079
+ });
1080
+ }
1081
+
1082
+ // Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
1083
+ // so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
1084
+ // with layout.
1085
+ function addPseudoElements(el) {
1086
+ // These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
1087
+ // layouts & getPseudoElement calling getComputedStyle.
1088
+ var jobs = [], classes = [];
1089
+ getPseudoElementClasses();
1090
+ findPseudoElements(el);
1091
+ runJobs();
1092
+
1093
+ function getPseudoElementClasses(){
1094
+ var findPsuedoEls = /:before|:after/;
1095
+ var sheets = document.styleSheets;
1096
+ for (var i = 0, j = sheets.length; i < j; i++) {
1097
+ try {
1098
+ var rules = sheets[i].cssRules;
1099
+ for (var k = 0, l = rules.length; k < l; k++) {
1100
+ if(findPsuedoEls.test(rules[k].selectorText)) {
1101
+ classes.push(rules[k].selectorText);
1102
+ }
1103
+ }
1104
+ }
1105
+ catch(e) { // will throw security exception for style sheets loaded from external domains
1106
+ }
1107
+ }
1108
+
1109
+ // Trim off the :after and :before (or ::after and ::before)
1110
+ for (i = 0, j = classes.length; i < j; i++) {
1111
+ classes[i] = classes[i].match(/(^[^:]*)/)[1];
1112
+ }
1113
+ }
1114
+
1115
+ // Using the list of elements we know how pseudo el styles, create fake pseudo elements.
1116
+ function findPseudoElements(el) {
1117
+ var els = document.querySelectorAll(classes.join(','));
1118
+ for(var i = 0, j = els.length; i < j; i++) {
1119
+ createPseudoElements(els[i]);
1120
+ }
1121
+ }
1122
+
1123
+ // Create pseudo elements & add them to a job queue.
1124
+ function createPseudoElements(el) {
1125
+ var before = getPseudoElement(el, ':before'),
1126
+ after = getPseudoElement(el, ':after');
1127
+
1128
+ if(before) {
1129
+ jobs.push({type: 'before', pseudo: before, el: el});
1130
+ }
1131
+
1132
+ if (after) {
1133
+ jobs.push({type: 'after', pseudo: after, el: el});
1134
+ }
1135
+ }
1136
+
1137
+ // Adds a class to the pseudo's parent to prevent the original before/after from messing
1138
+ // with layouts.
1139
+ // Execute the inserts & addClass() calls in a batch to prevent relayouts.
1140
+ function runJobs() {
1141
+ // Add Class
1142
+ jobs.forEach(function(job){
1143
+ addClass(job.el, pseudoHide + "-parent");
1144
+ });
1145
+
1146
+ // Insert el
1147
+ jobs.forEach(function(job){
1148
+ if(job.type === 'before'){
1149
+ job.el.insertBefore(job.pseudo, job.el.firstChild);
1150
+ } else {
1151
+ job.el.appendChild(job.pseudo);
1152
+ }
1153
+ });
1154
+ }
1155
+ }
1156
+
1157
+
1158
+
1159
+ // Delete our fake pseudo elements from the DOM. This will remove those actual elements
1160
+ // and the classes on their parents that hide the actual pseudo elements.
1161
+ // Note that NodeLists are 'live' collections so you can't use a for loop here. They are
1162
+ // actually deleted from the NodeList after each iteration.
1163
+ function removePseudoElements(){
1164
+ // delete pseudo elements
1165
+ body.removeChild(hidePseudoElementsStyles);
1166
+ var pseudos = document.getElementsByClassName(pseudoHide + "-element");
1167
+ while (pseudos.length) {
1168
+ pseudos[0].parentNode.removeChild(pseudos[0]);
1169
+ }
1170
+
1171
+ // Remove pseudo hiding classes
1172
+ var parents = document.getElementsByClassName(pseudoHide + "-parent");
1173
+ while(parents.length) {
1174
+ removeClass(parents[0], pseudoHide + "-parent");
1175
+ }
1176
+ }
1177
+
1178
+ function addClass (el, className) {
1179
+ if (el.classList) {
1180
+ el.classList.add(className);
1181
+ } else {
1182
+ el.className = el.className + " " + className;
1183
+ }
1184
+ }
1185
+
1186
+ function removeClass (el, className) {
1187
+ if (el.classList) {
1188
+ el.classList.remove(className);
1189
+ } else {
1190
+ el.className = el.className.replace(className, "").trim();
1191
+ }
1192
+ }
1193
+
1194
+ function hasClass (el, className) {
1195
+ return el.className.indexOf(className) > -1;
1196
+ }
1197
+
1198
+ // Note that this doesn't work in < IE8, but we don't support that anyhow
1199
+ function nodeListToArray (nodeList) {
1200
+ return Array.prototype.slice.call(nodeList);
1201
+ }
1202
+
1203
+ function documentWidth () {
1204
+ return Math.max(
1205
+ Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
1206
+ Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
1207
+ Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
1208
+ );
1209
+ }
1210
+
1211
+ function documentHeight () {
1212
+ return Math.max(
1213
+ Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
1214
+ Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
1215
+ Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
1216
+ );
1217
+ }
1218
+
1219
+ function getCSSInt(element, attribute) {
1220
+ var val = parseInt(getCSS(element, attribute), 10);
1221
+ return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html
1222
+ }
1223
+
1224
+ function renderRect (ctx, x, y, w, h, bgcolor) {
1225
+ if (bgcolor !== "transparent"){
1226
+ ctx.setVariable("fillStyle", bgcolor);
1227
+ ctx.fillRect(x, y, w, h);
1228
+ numDraws+=1;
1229
+ }
1230
+ }
1231
+
1232
+ function capitalize(m, p1, p2) {
1233
+ if (m.length > 0) {
1234
+ return p1 + p2.toUpperCase();
1235
+ }
1236
+ }
1237
+
1238
+ function textTransform (text, transform) {
1239
+ switch(transform){
1240
+ case "lowercase":
1241
+ return text.toLowerCase();
1242
+ case "capitalize":
1243
+ return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize);
1244
+ case "uppercase":
1245
+ return text.toUpperCase();
1246
+ default:
1247
+ return text;
1248
+ }
1249
+ }
1250
+
1251
+ function noLetterSpacing(letter_spacing) {
1252
+ return (/^(normal|none|0px)$/.test(letter_spacing));
1253
+ }
1254
+
1255
+ function drawText(currentText, x, y, ctx){
1256
+ if (currentText !== null && Util.trimText(currentText).length > 0) {
1257
+ ctx.fillText(currentText, x, y);
1258
+ numDraws+=1;
1259
+ }
1260
+ }
1261
+
1262
+ function setTextVariables(ctx, el, text_decoration, color) {
1263
+ var align = false,
1264
+ bold = getCSS(el, "fontWeight"),
1265
+ family = getCSS(el, "fontFamily"),
1266
+ size = getCSS(el, "fontSize"),
1267
+ shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
1268
+
1269
+ switch(parseInt(bold, 10)){
1270
+ case 401:
1271
+ bold = "bold";
1272
+ break;
1273
+ case 400:
1274
+ bold = "normal";
1275
+ break;
1276
+ }
1277
+
1278
+ ctx.setVariable("fillStyle", color);
1279
+ ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
1280
+ ctx.setVariable("textAlign", (align) ? "right" : "left");
1281
+
1282
+ if (shadows.length) {
1283
+ // TODO: support multiple text shadows
1284
+ // apply the first text shadow
1285
+ ctx.setVariable("shadowColor", shadows[0].color);
1286
+ ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
1287
+ ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
1288
+ ctx.setVariable("shadowBlur", shadows[0].blur);
1289
+ }
1290
+
1291
+ if (text_decoration !== "none"){
1292
+ return Util.Font(family, size, doc);
1293
+ }
1294
+ }
1295
+
1296
+ function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) {
1297
+ switch(text_decoration) {
1298
+ case "underline":
1299
+ // Draws a line at the baseline of the font
1300
+ // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
1301
+ renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color);
1302
+ break;
1303
+ case "overline":
1304
+ renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color);
1305
+ break;
1306
+ case "line-through":
1307
+ // TODO try and find exact position for line-through
1308
+ renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color);
1309
+ break;
1310
+ }
1311
+ }
1312
+
1313
+ function getTextBounds(state, text, textDecoration, isLast, transform) {
1314
+ var bounds;
1315
+ if (support.rangeBounds && !transform) {
1316
+ if (textDecoration !== "none" || Util.trimText(text).length !== 0) {
1317
+ bounds = textRangeBounds(text, state.node, state.textOffset);
1318
+ }
1319
+ state.textOffset += text.length;
1320
+ } else if (state.node && typeof state.node.nodeValue === "string" ){
1321
+ var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
1322
+ bounds = textWrapperBounds(state.node, transform);
1323
+ state.node = newTextNode;
1324
+ }
1325
+ return bounds;
1326
+ }
1327
+
1328
+ function textRangeBounds(text, textNode, textOffset) {
1329
+ var range = doc.createRange();
1330
+ range.setStart(textNode, textOffset);
1331
+ range.setEnd(textNode, textOffset + text.length);
1332
+ return range.getBoundingClientRect();
1333
+ }
1334
+
1335
+ function textWrapperBounds(oldTextNode, transform) {
1336
+ var parent = oldTextNode.parentNode,
1337
+ wrapElement = doc.createElement('wrapper'),
1338
+ backupText = oldTextNode.cloneNode(true);
1339
+
1340
+ wrapElement.appendChild(oldTextNode.cloneNode(true));
1341
+ parent.replaceChild(wrapElement, oldTextNode);
1342
+
1343
+ var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
1344
+ parent.replaceChild(backupText, wrapElement);
1345
+ return bounds;
1346
+ }
1347
+
1348
+ function renderText(el, textNode, stack) {
1349
+ var ctx = stack.ctx,
1350
+ color = getCSS(el, "color"),
1351
+ textDecoration = getCSS(el, "textDecoration"),
1352
+ textAlign = getCSS(el, "textAlign"),
1353
+ metrics,
1354
+ textList,
1355
+ state = {
1356
+ node: textNode,
1357
+ textOffset: 0
1358
+ };
1359
+
1360
+ if (Util.trimText(textNode.nodeValue).length > 0) {
1361
+ textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
1362
+ textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
1363
+
1364
+ textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
1365
+ textNode.nodeValue.split(/(\b| )/)
1366
+ : textNode.nodeValue.split("");
1367
+
1368
+ metrics = setTextVariables(ctx, el, textDecoration, color);
1369
+
1370
+ if (options.chinese) {
1371
+ textList.forEach(function(word, index) {
1372
+ if (/.*[\u4E00-\u9FA5].*$/.test(word)) {
1373
+ word = word.split("");
1374
+ word.unshift(index, 1);
1375
+ textList.splice.apply(textList, word);
1376
+ }
1377
+ });
1378
+ }
1379
+
1380
+ textList.forEach(function(text, index) {
1381
+ var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
1382
+ if (bounds) {
1383
+ drawText(text, bounds.left, bounds.bottom, ctx);
1384
+ renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
1385
+ }
1386
+ });
1387
+ }
1388
+ }
1389
+
1390
+ function listPosition (element, val) {
1391
+ var boundElement = doc.createElement( "boundelement" ),
1392
+ originalType,
1393
+ bounds;
1394
+
1395
+ boundElement.style.display = "inline";
1396
+
1397
+ originalType = element.style.listStyleType;
1398
+ element.style.listStyleType = "none";
1399
+
1400
+ boundElement.appendChild(doc.createTextNode(val));
1401
+
1402
+ element.insertBefore(boundElement, element.firstChild);
1403
+
1404
+ bounds = Util.Bounds(boundElement);
1405
+ element.removeChild(boundElement);
1406
+ element.style.listStyleType = originalType;
1407
+ return bounds;
1408
+ }
1409
+
1410
+ function elementIndex(el) {
1411
+ var i = -1,
1412
+ count = 1,
1413
+ childs = el.parentNode.childNodes;
1414
+
1415
+ if (el.parentNode) {
1416
+ while(childs[++i] !== el) {
1417
+ if (childs[i].nodeType === 1) {
1418
+ count++;
1419
+ }
1420
+ }
1421
+ return count;
1422
+ } else {
1423
+ return -1;
1424
+ }
1425
+ }
1426
+
1427
+ function listItemText(element, type) {
1428
+ var currentIndex = elementIndex(element), text;
1429
+ switch(type){
1430
+ case "decimal":
1431
+ text = currentIndex;
1432
+ break;
1433
+ case "decimal-leading-zero":
1434
+ text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString();
1435
+ break;
1436
+ case "upper-roman":
1437
+ text = _html2canvas.Generate.ListRoman( currentIndex );
1438
+ break;
1439
+ case "lower-roman":
1440
+ text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase();
1441
+ break;
1442
+ case "lower-alpha":
1443
+ text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase();
1444
+ break;
1445
+ case "upper-alpha":
1446
+ text = _html2canvas.Generate.ListAlpha( currentIndex );
1447
+ break;
1448
+ }
1449
+
1450
+ return text + ". ";
1451
+ }
1452
+
1453
+ function renderListItem(element, stack, elBounds) {
1454
+ var x,
1455
+ text,
1456
+ ctx = stack.ctx,
1457
+ type = getCSS(element, "listStyleType"),
1458
+ listBounds;
1459
+
1460
+ if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) {
1461
+ text = listItemText(element, type);
1462
+ listBounds = listPosition(element, text);
1463
+ setTextVariables(ctx, element, "none", getCSS(element, "color"));
1464
+
1465
+ if (getCSS(element, "listStylePosition") === "inside") {
1466
+ ctx.setVariable("textAlign", "left");
1467
+ x = elBounds.left;
1468
+ } else {
1469
+ return;
1470
+ }
1471
+
1472
+ drawText(text, x, listBounds.bottom, ctx);
1473
+ }
1474
+ }
1475
+
1476
+ function loadImage (src){
1477
+ var img = images[src];
1478
+ return (img && img.succeeded === true) ? img.img : false;
1479
+ }
1480
+
1481
+ function clipBounds(src, dst){
1482
+ var x = Math.max(src.left, dst.left),
1483
+ y = Math.max(src.top, dst.top),
1484
+ x2 = Math.min((src.left + src.width), (dst.left + dst.width)),
1485
+ y2 = Math.min((src.top + src.height), (dst.top + dst.height));
1486
+
1487
+ return {
1488
+ left:x,
1489
+ top:y,
1490
+ width:x2-x,
1491
+ height:y2-y
1492
+ };
1493
+ }
1494
+
1495
+ function setZ(element, stack, parentStack){
1496
+ var newContext,
1497
+ isPositioned = stack.cssPosition !== 'static',
1498
+ zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
1499
+ opacity = getCSS(element, 'opacity'),
1500
+ isFloated = getCSS(element, 'cssFloat') !== 'none';
1501
+
1502
+ // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
1503
+ // When a new stacking context should be created:
1504
+ // the root element (HTML),
1505
+ // positioned (absolutely or relatively) with a z-index value other than "auto",
1506
+ // elements with an opacity value less than 1. (See the specification for opacity),
1507
+ // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
1508
+
1509
+ stack.zIndex = newContext = h2czContext(zIndex);
1510
+ newContext.isPositioned = isPositioned;
1511
+ newContext.isFloated = isFloated;
1512
+ newContext.opacity = opacity;
1513
+ newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);
1514
+ newContext.depth = parentStack ? (parentStack.zIndex.depth + 1) : 0;
1515
+
1516
+ if (parentStack) {
1517
+ parentStack.zIndex.children.push(stack);
1518
+ }
1519
+ }
1520
+
1521
+ function h2czContext(zindex) {
1522
+ return {
1523
+ depth: 0,
1524
+ zindex: zindex,
1525
+ children: []
1526
+ };
1527
+ }
1528
+
1529
+ function renderImage(ctx, element, image, bounds, borders) {
1530
+
1531
+ var paddingLeft = getCSSInt(element, 'paddingLeft'),
1532
+ paddingTop = getCSSInt(element, 'paddingTop'),
1533
+ paddingRight = getCSSInt(element, 'paddingRight'),
1534
+ paddingBottom = getCSSInt(element, 'paddingBottom');
1535
+
1536
+ drawImage(
1537
+ ctx,
1538
+ image,
1539
+ 0, //sx
1540
+ 0, //sy
1541
+ image.width, //sw
1542
+ image.height, //sh
1543
+ bounds.left + paddingLeft + borders[3].width, //dx
1544
+ bounds.top + paddingTop + borders[0].width, // dy
1545
+ bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw
1546
+ bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh
1547
+ );
1548
+ }
1549
+
1550
+ function getBorderData(element) {
1551
+ return ["Top", "Right", "Bottom", "Left"].map(function(side) {
1552
+ return {
1553
+ width: getCSSInt(element, 'border' + side + 'Width'),
1554
+ color: getCSS(element, 'border' + side + 'Color')
1555
+ };
1556
+ });
1557
+ }
1558
+
1559
+ function getBorderRadiusData(element) {
1560
+ return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
1561
+ return getCSS(element, 'border' + side + 'Radius');
1562
+ });
1563
+ }
1564
+
1565
+ function getCurvePoints(x, y, r1, r2) {
1566
+ var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
1567
+ var ox = (r1) * kappa, // control point offset horizontal
1568
+ oy = (r2) * kappa, // control point offset vertical
1569
+ xm = x + r1, // x-middle
1570
+ ym = y + r2; // y-middle
1571
+ return {
1572
+ topLeft: bezierCurve({
1573
+ x:x,
1574
+ y:ym
1575
+ }, {
1576
+ x:x,
1577
+ y:ym - oy
1578
+ }, {
1579
+ x:xm - ox,
1580
+ y:y
1581
+ }, {
1582
+ x:xm,
1583
+ y:y
1584
+ }),
1585
+ topRight: bezierCurve({
1586
+ x:x,
1587
+ y:y
1588
+ }, {
1589
+ x:x + ox,
1590
+ y:y
1591
+ }, {
1592
+ x:xm,
1593
+ y:ym - oy
1594
+ }, {
1595
+ x:xm,
1596
+ y:ym
1597
+ }),
1598
+ bottomRight: bezierCurve({
1599
+ x:xm,
1600
+ y:y
1601
+ }, {
1602
+ x:xm,
1603
+ y:y + oy
1604
+ }, {
1605
+ x:x + ox,
1606
+ y:ym
1607
+ }, {
1608
+ x:x,
1609
+ y:ym
1610
+ }),
1611
+ bottomLeft: bezierCurve({
1612
+ x:xm,
1613
+ y:ym
1614
+ }, {
1615
+ x:xm - ox,
1616
+ y:ym
1617
+ }, {
1618
+ x:x,
1619
+ y:y + oy
1620
+ }, {
1621
+ x:x,
1622
+ y:y
1623
+ })
1624
+ };
1625
+ }
1626
+
1627
+ function bezierCurve(start, startControl, endControl, end) {
1628
+
1629
+ var lerp = function (a, b, t) {
1630
+ return {
1631
+ x:a.x + (b.x - a.x) * t,
1632
+ y:a.y + (b.y - a.y) * t
1633
+ };
1634
+ };
1635
+
1636
+ return {
1637
+ start: start,
1638
+ startControl: startControl,
1639
+ endControl: endControl,
1640
+ end: end,
1641
+ subdivide: function(t) {
1642
+ var ab = lerp(start, startControl, t),
1643
+ bc = lerp(startControl, endControl, t),
1644
+ cd = lerp(endControl, end, t),
1645
+ abbc = lerp(ab, bc, t),
1646
+ bccd = lerp(bc, cd, t),
1647
+ dest = lerp(abbc, bccd, t);
1648
+ return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
1649
+ },
1650
+ curveTo: function(borderArgs) {
1651
+ borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
1652
+ },
1653
+ curveToReversed: function(borderArgs) {
1654
+ borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
1655
+ }
1656
+ };
1657
+ }
1658
+
1659
+ function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
1660
+ if (radius1[0] > 0 || radius1[1] > 0) {
1661
+ borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
1662
+ corner1[0].curveTo(borderArgs);
1663
+ corner1[1].curveTo(borderArgs);
1664
+ } else {
1665
+ borderArgs.push(["line", x, y]);
1666
+ }
1667
+
1668
+ if (radius2[0] > 0 || radius2[1] > 0) {
1669
+ borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
1670
+ }
1671
+ }
1672
+
1673
+ function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
1674
+ var borderArgs = [];
1675
+
1676
+ if (radius1[0] > 0 || radius1[1] > 0) {
1677
+ borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
1678
+ outer1[1].curveTo(borderArgs);
1679
+ } else {
1680
+ borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
1681
+ }
1682
+
1683
+ if (radius2[0] > 0 || radius2[1] > 0) {
1684
+ borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
1685
+ outer2[0].curveTo(borderArgs);
1686
+ borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
1687
+ inner2[0].curveToReversed(borderArgs);
1688
+ } else {
1689
+ borderArgs.push([ "line", borderData.c2[0], borderData.c2[1]]);
1690
+ borderArgs.push([ "line", borderData.c3[0], borderData.c3[1]]);
1691
+ }
1692
+
1693
+ if (radius1[0] > 0 || radius1[1] > 0) {
1694
+ borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
1695
+ inner1[1].curveToReversed(borderArgs);
1696
+ } else {
1697
+ borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]);
1698
+ }
1699
+
1700
+ return borderArgs;
1701
+ }
1702
+
1703
+ function calculateCurvePoints(bounds, borderRadius, borders) {
1704
+
1705
+ var x = bounds.left,
1706
+ y = bounds.top,
1707
+ width = bounds.width,
1708
+ height = bounds.height,
1709
+
1710
+ tlh = borderRadius[0][0],
1711
+ tlv = borderRadius[0][1],
1712
+ trh = borderRadius[1][0],
1713
+ trv = borderRadius[1][1],
1714
+ brh = borderRadius[2][0],
1715
+ brv = borderRadius[2][1],
1716
+ blh = borderRadius[3][0],
1717
+ blv = borderRadius[3][1],
1718
+
1719
+ topWidth = width - trh,
1720
+ rightHeight = height - brv,
1721
+ bottomWidth = width - brh,
1722
+ leftHeight = height - blv;
1723
+
1724
+ return {
1725
+ topLeftOuter: getCurvePoints(
1726
+ x,
1727
+ y,
1728
+ tlh,
1729
+ tlv
1730
+ ).topLeft.subdivide(0.5),
1731
+
1732
+ topLeftInner: getCurvePoints(
1733
+ x + borders[3].width,
1734
+ y + borders[0].width,
1735
+ Math.max(0, tlh - borders[3].width),
1736
+ Math.max(0, tlv - borders[0].width)
1737
+ ).topLeft.subdivide(0.5),
1738
+
1739
+ topRightOuter: getCurvePoints(
1740
+ x + topWidth,
1741
+ y,
1742
+ trh,
1743
+ trv
1744
+ ).topRight.subdivide(0.5),
1745
+
1746
+ topRightInner: getCurvePoints(
1747
+ x + Math.min(topWidth, width + borders[3].width),
1748
+ y + borders[0].width,
1749
+ (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
1750
+ trv - borders[0].width
1751
+ ).topRight.subdivide(0.5),
1752
+
1753
+ bottomRightOuter: getCurvePoints(
1754
+ x + bottomWidth,
1755
+ y + rightHeight,
1756
+ brh,
1757
+ brv
1758
+ ).bottomRight.subdivide(0.5),
1759
+
1760
+ bottomRightInner: getCurvePoints(
1761
+ x + Math.min(bottomWidth, width + borders[3].width),
1762
+ y + Math.min(rightHeight, height + borders[0].width),
1763
+ Math.max(0, brh - borders[1].width),
1764
+ Math.max(0, brv - borders[2].width)
1765
+ ).bottomRight.subdivide(0.5),
1766
+
1767
+ bottomLeftOuter: getCurvePoints(
1768
+ x,
1769
+ y + leftHeight,
1770
+ blh,
1771
+ blv
1772
+ ).bottomLeft.subdivide(0.5),
1773
+
1774
+ bottomLeftInner: getCurvePoints(
1775
+ x + borders[3].width,
1776
+ y + leftHeight,
1777
+ Math.max(0, blh - borders[3].width),
1778
+ Math.max(0, blv - borders[2].width)
1779
+ ).bottomLeft.subdivide(0.5)
1780
+ };
1781
+ }
1782
+
1783
+ function getBorderClip(element, borderPoints, borders, radius, bounds) {
1784
+ var backgroundClip = getCSS(element, 'backgroundClip'),
1785
+ borderArgs = [];
1786
+
1787
+ switch(backgroundClip) {
1788
+ case "content-box":
1789
+ case "padding-box":
1790
+ parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
1791
+ parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
1792
+ parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
1793
+ parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
1794
+ break;
1795
+
1796
+ default:
1797
+ parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
1798
+ parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
1799
+ parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
1800
+ parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
1801
+ break;
1802
+ }
1803
+
1804
+ return borderArgs;
1805
+ }
1806
+
1807
+ function parseBorders(element, bounds, borders){
1808
+ var x = bounds.left,
1809
+ y = bounds.top,
1810
+ width = bounds.width,
1811
+ height = bounds.height,
1812
+ borderSide,
1813
+ bx,
1814
+ by,
1815
+ bw,
1816
+ bh,
1817
+ borderArgs,
1818
+ // http://www.w3.org/TR/css3-background/#the-border-radius
1819
+ borderRadius = getBorderRadiusData(element),
1820
+ borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
1821
+ borderData = {
1822
+ clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
1823
+ borders: []
1824
+ };
1825
+
1826
+ for (borderSide = 0; borderSide < 4; borderSide++) {
1827
+
1828
+ if (borders[borderSide].width > 0) {
1829
+ bx = x;
1830
+ by = y;
1831
+ bw = width;
1832
+ bh = height - (borders[2].width);
1833
+
1834
+ switch(borderSide) {
1835
+ case 0:
1836
+ // top border
1837
+ bh = borders[0].width;
1838
+
1839
+ borderArgs = drawSide({
1840
+ c1: [bx, by],
1841
+ c2: [bx + bw, by],
1842
+ c3: [bx + bw - borders[1].width, by + bh],
1843
+ c4: [bx + borders[3].width, by + bh]
1844
+ }, borderRadius[0], borderRadius[1],
1845
+ borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
1846
+ break;
1847
+ case 1:
1848
+ // right border
1849
+ bx = x + width - (borders[1].width);
1850
+ bw = borders[1].width;
1851
+
1852
+ borderArgs = drawSide({
1853
+ c1: [bx + bw, by],
1854
+ c2: [bx + bw, by + bh + borders[2].width],
1855
+ c3: [bx, by + bh],
1856
+ c4: [bx, by + borders[0].width]
1857
+ }, borderRadius[1], borderRadius[2],
1858
+ borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
1859
+ break;
1860
+ case 2:
1861
+ // bottom border
1862
+ by = (by + height) - (borders[2].width);
1863
+ bh = borders[2].width;
1864
+
1865
+ borderArgs = drawSide({
1866
+ c1: [bx + bw, by + bh],
1867
+ c2: [bx, by + bh],
1868
+ c3: [bx + borders[3].width, by],
1869
+ c4: [bx + bw - borders[3].width, by]
1870
+ }, borderRadius[2], borderRadius[3],
1871
+ borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
1872
+ break;
1873
+ case 3:
1874
+ // left border
1875
+ bw = borders[3].width;
1876
+
1877
+ borderArgs = drawSide({
1878
+ c1: [bx, by + bh + borders[2].width],
1879
+ c2: [bx, by],
1880
+ c3: [bx + bw, by + borders[0].width],
1881
+ c4: [bx + bw, by + bh]
1882
+ }, borderRadius[3], borderRadius[0],
1883
+ borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
1884
+ break;
1885
+ }
1886
+
1887
+ borderData.borders.push({
1888
+ args: borderArgs,
1889
+ color: borders[borderSide].color
1890
+ });
1891
+
1892
+ }
1893
+ }
1894
+
1895
+ return borderData;
1896
+ }
1897
+
1898
+ function createShape(ctx, args) {
1899
+ var shape = ctx.drawShape();
1900
+ args.forEach(function(border, index) {
1901
+ shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
1902
+ });
1903
+ return shape;
1904
+ }
1905
+
1906
+ function renderBorders(ctx, borderArgs, color) {
1907
+ if (color !== "transparent") {
1908
+ ctx.setVariable( "fillStyle", color);
1909
+ createShape(ctx, borderArgs);
1910
+ ctx.fill();
1911
+ numDraws+=1;
1912
+ }
1913
+ }
1914
+
1915
+ function renderFormValue (el, bounds, stack){
1916
+
1917
+ var valueWrap = doc.createElement('valuewrap'),
1918
+ cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'],
1919
+ textValue,
1920
+ textNode;
1921
+
1922
+ cssPropertyArray.forEach(function(property) {
1923
+ try {
1924
+ valueWrap.style[property] = getCSS(el, property);
1925
+ } catch(e) {
1926
+ // Older IE has issues with "border"
1927
+ Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
1928
+ }
1929
+ });
1930
+
1931
+ valueWrap.style.borderColor = "black";
1932
+ valueWrap.style.borderStyle = "solid";
1933
+ valueWrap.style.display = "block";
1934
+ valueWrap.style.position = "absolute";
1935
+
1936
+ if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){
1937
+ valueWrap.style.lineHeight = getCSS(el, "height");
1938
+ }
1939
+
1940
+ valueWrap.style.top = bounds.top + "px";
1941
+ valueWrap.style.left = bounds.left + "px";
1942
+
1943
+ textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value;
1944
+ if(!textValue) {
1945
+ textValue = el.placeholder;
1946
+ }
1947
+
1948
+ textNode = doc.createTextNode(textValue);
1949
+
1950
+ valueWrap.appendChild(textNode);
1951
+ body.appendChild(valueWrap);
1952
+
1953
+ renderText(el, textNode, stack);
1954
+ body.removeChild(valueWrap);
1955
+ }
1956
+
1957
+ function drawImage (ctx) {
1958
+ ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
1959
+ numDraws+=1;
1960
+ }
1961
+
1962
+ function getPseudoElement(el, which) {
1963
+ var elStyle = window.getComputedStyle(el, which);
1964
+ var parentStyle = window.getComputedStyle(el);
1965
+ // If no content attribute is present, the pseudo element is hidden,
1966
+ // or the parent has a content property equal to the content on the pseudo element,
1967
+ // move along.
1968
+ if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
1969
+ elStyle.display === "none" || parentStyle.content === elStyle.content) {
1970
+ return;
1971
+ }
1972
+ var content = elStyle.content + '';
1973
+
1974
+ // Strip inner quotes
1975
+ if(content[0] === "'" || content[0] === "\"") {
1976
+ content = content.replace(/(^['"])|(['"]$)/g, '');
1977
+ }
1978
+
1979
+ var isImage = content.substr( 0, 3 ) === 'url',
1980
+ elps = document.createElement( isImage ? 'img' : 'span' );
1981
+
1982
+ elps.className = pseudoHide + "-element ";
1983
+
1984
+ Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
1985
+ // Prevent assigning of read only CSS Rules, ex. length, parentRule
1986
+ try {
1987
+ elps.style[prop] = elStyle[prop];
1988
+ } catch (e) {
1989
+ Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
1990
+ }
1991
+ });
1992
+
1993
+ if(isImage) {
1994
+ elps.src = Util.parseBackgroundImage(content)[0].args[0];
1995
+ } else {
1996
+ elps.innerHTML = content;
1997
+ }
1998
+ return elps;
1999
+ }
2000
+
2001
+ function indexedProperty(property) {
2002
+ return (isNaN(window.parseInt(property, 10)));
2003
+ }
2004
+
2005
+ function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
2006
+ var offsetX = Math.round(bounds.left + backgroundPosition.left),
2007
+ offsetY = Math.round(bounds.top + backgroundPosition.top);
2008
+
2009
+ ctx.createPattern(image);
2010
+ ctx.translate(offsetX, offsetY);
2011
+ ctx.fill();
2012
+ ctx.translate(-offsetX, -offsetY);
2013
+ }
2014
+
2015
+ function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
2016
+ var args = [];
2017
+ args.push(["line", Math.round(left), Math.round(top)]);
2018
+ args.push(["line", Math.round(left + width), Math.round(top)]);
2019
+ args.push(["line", Math.round(left + width), Math.round(height + top)]);
2020
+ args.push(["line", Math.round(left), Math.round(height + top)]);
2021
+ createShape(ctx, args);
2022
+ ctx.save();
2023
+ ctx.clip();
2024
+ renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
2025
+ ctx.restore();
2026
+ }
2027
+
2028
+ function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
2029
+ renderRect(
2030
+ ctx,
2031
+ backgroundBounds.left,
2032
+ backgroundBounds.top,
2033
+ backgroundBounds.width,
2034
+ backgroundBounds.height,
2035
+ bgcolor
2036
+ );
2037
+ }
2038
+
2039
+ function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
2040
+ var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
2041
+ backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
2042
+ backgroundRepeat = Util.BackgroundRepeat(el, imageIndex);
2043
+
2044
+ image = resizeImage(image, backgroundSize);
2045
+
2046
+ switch (backgroundRepeat) {
2047
+ case "repeat-x":
2048
+ case "repeat no-repeat":
2049
+ backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
2050
+ bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
2051
+ break;
2052
+ case "repeat-y":
2053
+ case "no-repeat repeat":
2054
+ backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
2055
+ bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
2056
+ break;
2057
+ case "no-repeat":
2058
+ backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
2059
+ bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
2060
+ break;
2061
+ default:
2062
+ renderBackgroundRepeat(ctx, image, backgroundPosition, {
2063
+ top: bounds.top,
2064
+ left: bounds.left,
2065
+ width: image.width,
2066
+ height: image.height
2067
+ });
2068
+ break;
2069
+ }
2070
+ }
2071
+
2072
+ function renderBackgroundImage(element, bounds, ctx) {
2073
+ var backgroundImage = getCSS(element, "backgroundImage"),
2074
+ backgroundImages = Util.parseBackgroundImage(backgroundImage),
2075
+ image,
2076
+ imageIndex = backgroundImages.length;
2077
+
2078
+ while(imageIndex--) {
2079
+ backgroundImage = backgroundImages[imageIndex];
2080
+
2081
+ if (!backgroundImage.args || backgroundImage.args.length === 0) {
2082
+ continue;
2083
+ }
2084
+
2085
+ var key = backgroundImage.method === 'url' ?
2086
+ backgroundImage.args[0] :
2087
+ backgroundImage.value;
2088
+
2089
+ image = loadImage(key);
2090
+
2091
+ // TODO add support for background-origin
2092
+ if (image) {
2093
+ renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
2094
+ } else {
2095
+ Util.log("html2canvas: Error loading background:", backgroundImage);
2096
+ }
2097
+ }
2098
+ }
2099
+
2100
+ function resizeImage(image, bounds) {
2101
+ if(image.width === bounds.width && image.height === bounds.height) {
2102
+ return image;
2103
+ }
2104
+
2105
+ var ctx, canvas = doc.createElement('canvas');
2106
+ canvas.width = bounds.width;
2107
+ canvas.height = bounds.height;
2108
+ ctx = canvas.getContext("2d");
2109
+ drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height );
2110
+ return canvas;
2111
+ }
2112
+
2113
+ function setOpacity(ctx, element, parentStack) {
2114
+ return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
2115
+ }
2116
+
2117
+ function removePx(str) {
2118
+ return str.replace("px", "");
2119
+ }
2120
+
2121
+ function getTransform(element, parentStack) {
2122
+ var transformRegExp = /(matrix)\((.+)\)/;
2123
+ var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
2124
+ var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
2125
+
2126
+ transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);
2127
+
2128
+ var matrix;
2129
+ if (transform && transform !== "none") {
2130
+ var match = transform.match(transformRegExp);
2131
+ if (match) {
2132
+ switch(match[1]) {
2133
+ case "matrix":
2134
+ matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
2135
+ break;
2136
+ }
2137
+ }
2138
+ }
2139
+
2140
+ return {
2141
+ origin: transformOrigin,
2142
+ matrix: matrix
2143
+ };
2144
+ }
2145
+
2146
+ function createStack(element, parentStack, bounds, transform) {
2147
+ var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
2148
+ stack = {
2149
+ ctx: ctx,
2150
+ opacity: setOpacity(ctx, element, parentStack),
2151
+ cssPosition: getCSS(element, "position"),
2152
+ borders: getBorderData(element),
2153
+ transform: transform,
2154
+ clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null
2155
+ };
2156
+
2157
+ setZ(element, stack, parentStack);
2158
+
2159
+ // TODO correct overflow for absolute content residing under a static position
2160
+ if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
2161
+ stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
2162
+ }
2163
+
2164
+ return stack;
2165
+ }
2166
+
2167
+ function getBackgroundBounds(borders, bounds, clip) {
2168
+ var backgroundBounds = {
2169
+ left: bounds.left + borders[3].width,
2170
+ top: bounds.top + borders[0].width,
2171
+ width: bounds.width - (borders[1].width + borders[3].width),
2172
+ height: bounds.height - (borders[0].width + borders[2].width)
2173
+ };
2174
+
2175
+ if (clip) {
2176
+ backgroundBounds = clipBounds(backgroundBounds, clip);
2177
+ }
2178
+
2179
+ return backgroundBounds;
2180
+ }
2181
+
2182
+ function getBounds(element, transform) {
2183
+ var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
2184
+ transform.origin[0] += bounds.left;
2185
+ transform.origin[1] += bounds.top;
2186
+ return bounds;
2187
+ }
2188
+
2189
+ function renderElement(element, parentStack, ignoreBackground) {
2190
+ var transform = getTransform(element, parentStack),
2191
+ bounds = getBounds(element, transform),
2192
+ image,
2193
+ stack = createStack(element, parentStack, bounds, transform),
2194
+ borders = stack.borders,
2195
+ ctx = stack.ctx,
2196
+ backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
2197
+ borderData = parseBorders(element, bounds, borders),
2198
+ backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");
2199
+
2200
+
2201
+ createShape(ctx, borderData.clip);
2202
+
2203
+ ctx.save();
2204
+ ctx.clip();
2205
+
2206
+ if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
2207
+ renderBackgroundColor(ctx, bounds, backgroundColor);
2208
+ renderBackgroundImage(element, backgroundBounds, ctx);
2209
+ } else if (ignoreBackground) {
2210
+ stack.backgroundColor = backgroundColor;
2211
+ }
2212
+
2213
+ ctx.restore();
2214
+
2215
+ borderData.borders.forEach(function(border) {
2216
+ renderBorders(ctx, border.args, border.color);
2217
+ });
2218
+
2219
+ switch(element.nodeName){
2220
+ case "IMG":
2221
+ if ((image = loadImage(element.getAttribute('src')))) {
2222
+ renderImage(ctx, element, image, bounds, borders);
2223
+ } else {
2224
+ Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
2225
+ }
2226
+ break;
2227
+ case "INPUT":
2228
+ // TODO add all relevant type's, i.e. HTML5 new stuff
2229
+ // todo add support for placeholder attribute for browsers which support it
2230
+ if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){
2231
+ renderFormValue(element, bounds, stack);
2232
+ }
2233
+ break;
2234
+ case "TEXTAREA":
2235
+ if ((element.value || element.placeholder || "").length > 0){
2236
+ renderFormValue(element, bounds, stack);
2237
+ }
2238
+ break;
2239
+ case "SELECT":
2240
+ if ((element.options||element.placeholder || "").length > 0){
2241
+ renderFormValue(element, bounds, stack);
2242
+ }
2243
+ break;
2244
+ case "LI":
2245
+ renderListItem(element, stack, backgroundBounds);
2246
+ break;
2247
+ case "CANVAS":
2248
+ renderImage(ctx, element, element, bounds, borders);
2249
+ break;
2250
+ }
2251
+
2252
+ return stack;
2253
+ }
2254
+
2255
+ function isElementVisible(element) {
2256
+ return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
2257
+ }
2258
+
2259
+ function parseElement (element, stack, cb) {
2260
+ if (!cb) {
2261
+ cb = function(){};
2262
+ }
2263
+ if (isElementVisible(element)) {
2264
+ stack = renderElement(element, stack, false) || stack;
2265
+ if (!ignoreElementsRegExp.test(element.nodeName)) {
2266
+ return parseChildren(element, stack, cb);
2267
+ }
2268
+ }
2269
+ cb();
2270
+ }
2271
+
2272
+ function parseChildren(element, stack, cb) {
2273
+ var children = Util.Children(element);
2274
+ // After all nodes have processed, finished() will call the cb.
2275
+ // We add one and kick it off so this will still work when children.length === 0.
2276
+ // Note that unless async is true, this will happen synchronously, just will callbacks.
2277
+ var jobs = children.length + 1;
2278
+ finished();
2279
+
2280
+ if (options.async) {
2281
+ children.forEach(function(node) {
2282
+ // Don't block the page from rendering
2283
+ setTimeout(function(){ parseNode(node); }, 0);
2284
+ });
2285
+ } else {
2286
+ children.forEach(parseNode);
2287
+ }
2288
+
2289
+ function parseNode(node) {
2290
+ if (node.nodeType === node.ELEMENT_NODE) {
2291
+ parseElement(node, stack, finished);
2292
+ } else if (node.nodeType === node.TEXT_NODE) {
2293
+ renderText(element, node, stack);
2294
+ finished();
2295
+ } else {
2296
+ finished();
2297
+ }
2298
+ }
2299
+ function finished(el) {
2300
+ if (--jobs <= 0){
2301
+ Util.log("finished rendering " + children.length + " children.");
2302
+ cb();
2303
+ }
2304
+ }
2305
+ }
2306
+ };
2307
+ _html2canvas.Preload = function( options ) {
2308
+
2309
+ var images = {
2310
+ numLoaded: 0, // also failed are counted here
2311
+ numFailed: 0,
2312
+ numTotal: 0,
2313
+ cleanupDone: false
2314
+ },
2315
+ pageOrigin,
2316
+ Util = _html2canvas.Util,
2317
+ methods,
2318
+ i,
2319
+ count = 0,
2320
+ element = options.elements[0] || document.body,
2321
+ doc = element.ownerDocument,
2322
+ domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
2323
+ imgLen = domImages.length,
2324
+ link = doc.createElement("a"),
2325
+ supportCORS = (function( img ){
2326
+ return (img.crossOrigin !== undefined);
2327
+ })(new Image()),
2328
+ timeoutTimer;
2329
+
2330
+ link.href = window.location.href;
2331
+ pageOrigin = link.protocol + link.host;
2332
+
2333
+ function isSameOrigin(url){
2334
+ link.href = url;
2335
+ link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
2336
+ var origin = link.protocol + link.host;
2337
+ return (origin === pageOrigin);
2338
+ }
2339
+
2340
+ function start(){
2341
+ Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
2342
+ if (!images.firstRun && images.numLoaded >= images.numTotal){
2343
+ Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
2344
+
2345
+ if (typeof options.complete === "function"){
2346
+ options.complete(images);
2347
+ }
2348
+
2349
+ }
2350
+ }
2351
+
2352
+ // TODO modify proxy to serve images with CORS enabled, where available
2353
+ function proxyGetImage(url, img, imageObj){
2354
+ var callback_name,
2355
+ scriptUrl = options.proxy,
2356
+ script;
2357
+
2358
+ link.href = url;
2359
+ url = link.href; // work around for pages with base href="" set - WARNING: this may change the url
2360
+
2361
+ callback_name = 'html2canvas_' + (count++);
2362
+ imageObj.callbackname = callback_name;
2363
+
2364
+ if (scriptUrl.indexOf("?") > -1) {
2365
+ scriptUrl += "&";
2366
+ } else {
2367
+ scriptUrl += "?";
2368
+ }
2369
+ scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
2370
+ script = doc.createElement("script");
2371
+
2372
+ window[callback_name] = function(a){
2373
+ if (a.substring(0,6) === "error:"){
2374
+ imageObj.succeeded = false;
2375
+ images.numLoaded++;
2376
+ images.numFailed++;
2377
+ start();
2378
+ } else {
2379
+ setImageLoadHandlers(img, imageObj);
2380
+ img.src = a;
2381
+ }
2382
+ window[callback_name] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
2383
+ try {
2384
+ delete window[callback_name]; // for all browser that support this
2385
+ } catch(ex) {}
2386
+ script.parentNode.removeChild(script);
2387
+ script = null;
2388
+ delete imageObj.script;
2389
+ delete imageObj.callbackname;
2390
+ };
2391
+
2392
+ script.setAttribute("type", "text/javascript");
2393
+ script.setAttribute("src", scriptUrl);
2394
+ imageObj.script = script;
2395
+ window.document.body.appendChild(script);
2396
+
2397
+ }
2398
+
2399
+ function loadPseudoElement(element, type) {
2400
+ var style = window.getComputedStyle(element, type),
2401
+ content = style.content;
2402
+ if (content.substr(0, 3) === 'url') {
2403
+ methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
2404
+ }
2405
+ loadBackgroundImages(style.backgroundImage, element);
2406
+ }
2407
+
2408
+ function loadPseudoElementImages(element) {
2409
+ loadPseudoElement(element, ":before");
2410
+ loadPseudoElement(element, ":after");
2411
+ }
2412
+
2413
+ function loadGradientImage(backgroundImage, bounds) {
2414
+ var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);
2415
+
2416
+ if (img !== undefined){
2417
+ images[backgroundImage] = {
2418
+ img: img,
2419
+ succeeded: true
2420
+ };
2421
+ images.numTotal++;
2422
+ images.numLoaded++;
2423
+ start();
2424
+ }
2425
+ }
2426
+
2427
+ function invalidBackgrounds(background_image) {
2428
+ return (background_image && background_image.method && background_image.args && background_image.args.length > 0 );
2429
+ }
2430
+
2431
+ function loadBackgroundImages(background_image, el) {
2432
+ var bounds;
2433
+
2434
+ _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
2435
+ if (background_image.method === 'url') {
2436
+ methods.loadImage(background_image.args[0]);
2437
+ } else if(background_image.method.match(/\-?gradient$/)) {
2438
+ if(bounds === undefined) {
2439
+ bounds = _html2canvas.Util.Bounds(el);
2440
+ }
2441
+ loadGradientImage(background_image.value, bounds);
2442
+ }
2443
+ });
2444
+ }
2445
+
2446
+ function getImages (el) {
2447
+ var elNodeType = false;
2448
+
2449
+ // Firefox fails with permission denied on pages with iframes
2450
+ try {
2451
+ Util.Children(el).forEach(getImages);
2452
+ }
2453
+ catch( e ) {}
2454
+
2455
+ try {
2456
+ elNodeType = el.nodeType;
2457
+ } catch (ex) {
2458
+ elNodeType = false;
2459
+ Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
2460
+ }
2461
+
2462
+ if (elNodeType === 1 || elNodeType === undefined) {
2463
+ loadPseudoElementImages(el);
2464
+ try {
2465
+ loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
2466
+ } catch(e) {
2467
+ Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
2468
+ }
2469
+ loadBackgroundImages(el);
2470
+ }
2471
+ }
2472
+
2473
+ function setImageLoadHandlers(img, imageObj) {
2474
+ img.onload = function() {
2475
+ if ( imageObj.timer !== undefined ) {
2476
+ // CORS succeeded
2477
+ window.clearTimeout( imageObj.timer );
2478
+ }
2479
+
2480
+ images.numLoaded++;
2481
+ imageObj.succeeded = true;
2482
+ img.onerror = img.onload = null;
2483
+ start();
2484
+ };
2485
+ img.onerror = function() {
2486
+ if (img.crossOrigin === "anonymous") {
2487
+ // CORS failed
2488
+ window.clearTimeout( imageObj.timer );
2489
+
2490
+ // let's try with proxy instead
2491
+ if ( options.proxy ) {
2492
+ var src = img.src;
2493
+ img = new Image();
2494
+ imageObj.img = img;
2495
+ img.src = src;
2496
+
2497
+ proxyGetImage( img.src, img, imageObj );
2498
+ return;
2499
+ }
2500
+ }
2501
+
2502
+ images.numLoaded++;
2503
+ images.numFailed++;
2504
+ imageObj.succeeded = false;
2505
+ img.onerror = img.onload = null;
2506
+ start();
2507
+ };
2508
+ }
2509
+
2510
+ methods = {
2511
+ loadImage: function( src ) {
2512
+ var img, imageObj;
2513
+ if ( src && images[src] === undefined ) {
2514
+ img = new Image();
2515
+ if ( src.match(/data:image\/.*;base64,/i) ) {
2516
+ img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
2517
+ imageObj = images[src] = {
2518
+ img: img
2519
+ };
2520
+ images.numTotal++;
2521
+ setImageLoadHandlers(img, imageObj);
2522
+ } else if ( isSameOrigin( src ) || options.allowTaint === true ) {
2523
+ imageObj = images[src] = {
2524
+ img: img
2525
+ };
2526
+ images.numTotal++;
2527
+ setImageLoadHandlers(img, imageObj);
2528
+ img.src = src;
2529
+ } else if ( supportCORS && !options.allowTaint && options.useCORS ) {
2530
+ // attempt to load with CORS
2531
+
2532
+ img.crossOrigin = "anonymous";
2533
+ imageObj = images[src] = {
2534
+ img: img
2535
+ };
2536
+ images.numTotal++;
2537
+ setImageLoadHandlers(img, imageObj);
2538
+ img.src = src;
2539
+ } else if ( options.proxy ) {
2540
+ imageObj = images[src] = {
2541
+ img: img
2542
+ };
2543
+ images.numTotal++;
2544
+ proxyGetImage( src, img, imageObj );
2545
+ }
2546
+ }
2547
+
2548
+ },
2549
+ cleanupDOM: function(cause) {
2550
+ var img, src;
2551
+ if (!images.cleanupDone) {
2552
+ if (cause && typeof cause === "string") {
2553
+ Util.log("html2canvas: Cleanup because: " + cause);
2554
+ } else {
2555
+ Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
2556
+ }
2557
+
2558
+ for (src in images) {
2559
+ if (images.hasOwnProperty(src)) {
2560
+ img = images[src];
2561
+ if (typeof img === "object" && img.callbackname && img.succeeded === undefined) {
2562
+ // cancel proxy image request
2563
+ window[img.callbackname] = undefined; // to work with IE<9 // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
2564
+ try {
2565
+ delete window[img.callbackname]; // for all browser that support this
2566
+ } catch(ex) {}
2567
+ if (img.script && img.script.parentNode) {
2568
+ img.script.setAttribute("src", "about:blank"); // try to cancel running request
2569
+ img.script.parentNode.removeChild(img.script);
2570
+ }
2571
+ images.numLoaded++;
2572
+ images.numFailed++;
2573
+ Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
2574
+ }
2575
+ }
2576
+ }
2577
+
2578
+ // cancel any pending requests
2579
+ if(window.stop !== undefined) {
2580
+ window.stop();
2581
+ } else if(document.execCommand !== undefined) {
2582
+ document.execCommand("Stop", false);
2583
+ }
2584
+ if (document.close !== undefined) {
2585
+ document.close();
2586
+ }
2587
+ images.cleanupDone = true;
2588
+ if (!(cause && typeof cause === "string")) {
2589
+ start();
2590
+ }
2591
+ }
2592
+ },
2593
+
2594
+ renderingDone: function() {
2595
+ if (timeoutTimer) {
2596
+ window.clearTimeout(timeoutTimer);
2597
+ }
2598
+ }
2599
+ };
2600
+
2601
+ if (options.timeout > 0) {
2602
+ timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
2603
+ }
2604
+
2605
+ Util.log('html2canvas: Preload starts: finding background-images');
2606
+ images.firstRun = true;
2607
+
2608
+ getImages(element);
2609
+
2610
+ Util.log('html2canvas: Preload: Finding images');
2611
+ // load <img> images
2612
+ for (i = 0; i < imgLen; i+=1){
2613
+ methods.loadImage( domImages[i].getAttribute( "src" ) );
2614
+ }
2615
+
2616
+ images.firstRun = false;
2617
+ Util.log('html2canvas: Preload: Done.');
2618
+ if (images.numTotal === images.numLoaded) {
2619
+ start();
2620
+ }
2621
+
2622
+ return methods;
2623
+ };
2624
+
2625
+ _html2canvas.Renderer = function(parseQueue, options){
2626
+ function sortZindex(a, b) {
2627
+ if (a === 'children') {
2628
+ return -1;
2629
+ } else if (b === 'children') {
2630
+ return 1;
2631
+ } else {
2632
+ return a - b;
2633
+ }
2634
+ }
2635
+
2636
+ // http://www.w3.org/TR/CSS21/zindex.html
2637
+ function createRenderQueue(parseQueue) {
2638
+ var queue = [],
2639
+ rootContext;
2640
+
2641
+ rootContext = (function buildStackingContext(rootNode) {
2642
+ var rootContext = {};
2643
+ function insert(context, node, specialParent) {
2644
+ var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
2645
+ contextForChildren = context, // the stacking context for children
2646
+ isPositioned = node.zIndex.isPositioned,
2647
+ isFloated = node.zIndex.isFloated,
2648
+ stub = {node: node},
2649
+ childrenDest = specialParent; // where children without z-index should be pushed into
2650
+
2651
+ if (node.zIndex.ownStacking) {
2652
+ contextForChildren = stub.context = {
2653
+ children: [{node:node, children: []}]
2654
+ };
2655
+ childrenDest = undefined;
2656
+ } else if (isPositioned || isFloated) {
2657
+ childrenDest = stub.children = [];
2658
+ }
2659
+
2660
+ if (zi === 0 && specialParent) {
2661
+ specialParent.push(stub);
2662
+ } else {
2663
+ if (!context[zi]) { context[zi] = []; }
2664
+ context[zi].push(stub);
2665
+ }
2666
+
2667
+ node.zIndex.children.forEach(function(childNode) {
2668
+ insert(contextForChildren, childNode, childrenDest);
2669
+ });
2670
+ }
2671
+ insert(rootContext, rootNode);
2672
+ return rootContext;
2673
+ })(parseQueue);
2674
+
2675
+ function sortZ(context) {
2676
+ Object.keys(context).sort(sortZindex).forEach(function(zi) {
2677
+ var nonPositioned = [],
2678
+ floated = [],
2679
+ positioned = [],
2680
+ list = [];
2681
+
2682
+ // positioned after static
2683
+ context[zi].forEach(function(v) {
2684
+ if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
2685
+ // http://www.w3.org/TR/css3-color/#transparency
2686
+ // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’.
2687
+ positioned.push(v);
2688
+ } else if (v.node.zIndex.isFloated) {
2689
+ floated.push(v);
2690
+ } else {
2691
+ nonPositioned.push(v);
2692
+ }
2693
+ });
2694
+
2695
+ (function walk(arr) {
2696
+ arr.forEach(function(v) {
2697
+ list.push(v);
2698
+ if (v.children) { walk(v.children); }
2699
+ });
2700
+ })(nonPositioned.concat(floated, positioned));
2701
+
2702
+ list.forEach(function(v) {
2703
+ if (v.context) {
2704
+ sortZ(v.context);
2705
+ } else {
2706
+ queue.push(v.node);
2707
+ }
2708
+ });
2709
+ });
2710
+ }
2711
+
2712
+ sortZ(rootContext);
2713
+
2714
+ return queue;
2715
+ }
2716
+
2717
+ function getRenderer(rendererName) {
2718
+ var renderer;
2719
+
2720
+ if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
2721
+ renderer = _html2canvas.Renderer[rendererName](options);
2722
+ } else if (typeof rendererName === "function") {
2723
+ renderer = rendererName(options);
2724
+ } else {
2725
+ throw new Error("Unknown renderer");
2726
+ }
2727
+
2728
+ if ( typeof renderer !== "function" ) {
2729
+ throw new Error("Invalid renderer defined");
2730
+ }
2731
+ return renderer;
2732
+ }
2733
+
2734
+ return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
2735
+ };
2736
+
2737
+ _html2canvas.Util.Support = function (options, doc) {
2738
+
2739
+ function supportSVGRendering() {
2740
+ var img = new Image(),
2741
+ canvas = doc.createElement("canvas"),
2742
+ ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
2743
+ if (ctx === false) {
2744
+ return false;
2745
+ }
2746
+ canvas.width = canvas.height = 10;
2747
+ img.src = [
2748
+ "data:image/svg+xml,",
2749
+ "<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
2750
+ "<foreignObject width='10' height='10'>",
2751
+ "<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
2752
+ "sup",
2753
+ "</div>",
2754
+ "</foreignObject>",
2755
+ "</svg>"
2756
+ ].join("");
2757
+ try {
2758
+ ctx.drawImage(img, 0, 0);
2759
+ canvas.toDataURL();
2760
+ } catch(e) {
2761
+ return false;
2762
+ }
2763
+ _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
2764
+ return true;
2765
+ }
2766
+
2767
+ // Test whether we can use ranges to measure bounding boxes
2768
+ // Opera doesn't provide valid bounds.height/bottom even though it supports the method.
2769
+
2770
+ function supportRangeBounds() {
2771
+ var r, testElement, rangeBounds, rangeHeight, support = false;
2772
+
2773
+ if (doc.createRange) {
2774
+ r = doc.createRange();
2775
+ if (r.getBoundingClientRect) {
2776
+ testElement = doc.createElement('boundtest');
2777
+ testElement.style.height = "123px";
2778
+ testElement.style.display = "block";
2779
+ doc.body.appendChild(testElement);
2780
+
2781
+ r.selectNode(testElement);
2782
+ rangeBounds = r.getBoundingClientRect();
2783
+ rangeHeight = rangeBounds.height;
2784
+
2785
+ if (rangeHeight === 123) {
2786
+ support = true;
2787
+ }
2788
+ doc.body.removeChild(testElement);
2789
+ }
2790
+ }
2791
+
2792
+ return support;
2793
+ }
2794
+
2795
+ return {
2796
+ rangeBounds: supportRangeBounds(),
2797
+ svgRendering: options.svgRendering && supportSVGRendering()
2798
+ };
2799
+ };
2800
+ window.html2canvas = function(elements, opts) {
2801
+ elements = (elements.length) ? elements : [elements];
2802
+ var queue,
2803
+ canvas,
2804
+ options = {
2805
+ // general
2806
+ logging: false,
2807
+ elements: elements,
2808
+ background: "#fff",
2809
+
2810
+ // preload options
2811
+ proxy: null,
2812
+ timeout: 0, // no timeout
2813
+ useCORS: false, // try to load images as CORS (where available), before falling back to proxy
2814
+ allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true
2815
+
2816
+ // parse options
2817
+ svgRendering: false, // use svg powered rendering where available (FF11+)
2818
+ ignoreElements: "IFRAME|OBJECT|PARAM",
2819
+ useOverflow: true,
2820
+ letterRendering: false,
2821
+ chinese: false,
2822
+ async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird
2823
+
2824
+ // render options
2825
+ width: null,
2826
+ height: null,
2827
+ taintTest: true, // do a taint test with all images before applying to canvas
2828
+ renderer: "Canvas"
2829
+ };
2830
+
2831
+ options = _html2canvas.Util.Extend(opts, options);
2832
+
2833
+ _html2canvas.logging = options.logging;
2834
+ options.complete = function( images ) {
2835
+
2836
+ if (typeof options.onpreloaded === "function") {
2837
+ if ( options.onpreloaded( images ) === false ) {
2838
+ return;
2839
+ }
2840
+ }
2841
+ _html2canvas.Parse( images, options, function(queue) {
2842
+ if (typeof options.onparsed === "function") {
2843
+ if ( options.onparsed( queue ) === false ) {
2844
+ return;
2845
+ }
2846
+ }
2847
+
2848
+ canvas = _html2canvas.Renderer( queue, options );
2849
+
2850
+ if (typeof options.onrendered === "function") {
2851
+ options.onrendered( canvas );
2852
+ }
2853
+ });
2854
+ };
2855
+
2856
+ // for pages without images, we still want this to be async, i.e. return methods before executing
2857
+ window.setTimeout( function(){
2858
+ _html2canvas.Preload( options );
2859
+ }, 0 );
2860
+
2861
+ return {
2862
+ render: function( queue, opts ) {
2863
+ return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) );
2864
+ },
2865
+ parse: function( images, opts ) {
2866
+ return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) );
2867
+ },
2868
+ preload: function( opts ) {
2869
+ return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
2870
+ },
2871
+ log: _html2canvas.Util.log
2872
+ };
2873
+ };
2874
+
2875
+ window.html2canvas.log = _html2canvas.Util.log; // for renderers
2876
+ window.html2canvas.Renderer = {
2877
+ Canvas: undefined // We are assuming this will be used
2878
+ };
2879
+ _html2canvas.Renderer.Canvas = function(options) {
2880
+ options = options || {};
2881
+
2882
+ var doc = document,
2883
+ safeImages = [],
2884
+ testCanvas = document.createElement("canvas"),
2885
+ testctx = testCanvas.getContext("2d"),
2886
+ Util = _html2canvas.Util,
2887
+ canvas = options.canvas || doc.createElement('canvas');
2888
+
2889
+ function createShape(ctx, args) {
2890
+ ctx.beginPath();
2891
+ args.forEach(function(arg) {
2892
+ ctx[arg.name].apply(ctx, arg['arguments']);
2893
+ });
2894
+ ctx.closePath();
2895
+ }
2896
+
2897
+ function safeImage(item) {
2898
+ if (safeImages.indexOf(item['arguments'][0].src) === -1) {
2899
+ testctx.drawImage(item['arguments'][0], 0, 0);
2900
+ try {
2901
+ testctx.getImageData(0, 0, 1, 1);
2902
+ } catch(e) {
2903
+ testCanvas = doc.createElement("canvas");
2904
+ testctx = testCanvas.getContext("2d");
2905
+ return false;
2906
+ }
2907
+ safeImages.push(item['arguments'][0].src);
2908
+ }
2909
+ return true;
2910
+ }
2911
+
2912
+ function renderItem(ctx, item) {
2913
+ switch(item.type){
2914
+ case "variable":
2915
+ ctx[item.name] = item['arguments'];
2916
+ break;
2917
+ case "function":
2918
+ switch(item.name) {
2919
+ case "createPattern":
2920
+ if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
2921
+ try {
2922
+ ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
2923
+ } catch(e) {
2924
+ Util.log("html2canvas: Renderer: Error creating pattern", e.message);
2925
+ }
2926
+ }
2927
+ break;
2928
+ case "drawShape":
2929
+ createShape(ctx, item['arguments']);
2930
+ break;
2931
+ case "drawImage":
2932
+ if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
2933
+ if (!options.taintTest || (options.taintTest && safeImage(item))) {
2934
+ ctx.drawImage.apply( ctx, item['arguments'] );
2935
+ }
2936
+ }
2937
+ break;
2938
+ default:
2939
+ ctx[item.name].apply(ctx, item['arguments']);
2940
+ }
2941
+ break;
2942
+ }
2943
+ }
2944
+
2945
+ return function(parsedData, options, document, queue, _html2canvas) {
2946
+ var ctx = canvas.getContext("2d"),
2947
+ newCanvas,
2948
+ bounds,
2949
+ fstyle,
2950
+ zStack = parsedData.stack;
2951
+
2952
+ canvas.width = canvas.style.width = options.width || zStack.ctx.width;
2953
+ canvas.height = canvas.style.height = options.height || zStack.ctx.height;
2954
+
2955
+ fstyle = ctx.fillStyle;
2956
+ ctx.fillStyle = (Util.isTransparent(parsedData.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
2957
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
2958
+ ctx.fillStyle = fstyle;
2959
+ queue.forEach(function(storageContext) {
2960
+ // set common settings for canvas
2961
+ ctx.textBaseline = "bottom";
2962
+ ctx.save();
2963
+
2964
+ if (storageContext.transform.matrix) {
2965
+ ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
2966
+ ctx.transform.apply(ctx, storageContext.transform.matrix);
2967
+ ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
2968
+ }
2969
+
2970
+ if (storageContext.clip){
2971
+ ctx.beginPath();
2972
+ ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
2973
+ ctx.clip();
2974
+ }
2975
+
2976
+ if (storageContext.ctx.storage) {
2977
+ storageContext.ctx.storage.forEach(function(item) {
2978
+ renderItem(ctx, item);
2979
+ });
2980
+ }
2981
+
2982
+ ctx.restore();
2983
+ });
2984
+
2985
+ Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
2986
+
2987
+ if (options.elements.length === 1) {
2988
+ if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
2989
+ // crop image to the bounds of selected (single) element
2990
+ bounds = _html2canvas.Util.Bounds(options.elements[0]);
2991
+ newCanvas = document.createElement('canvas');
2992
+
2993
+
2994
+ newCanvas.width = Math.ceil(bounds.width);
2995
+ newCanvas.height = Math.ceil(bounds.height);
2996
+
2997
+ ctx = newCanvas.getContext("2d");
2998
+ ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
2999
+
3000
+
3001
+
3002
+ canvas = null;
3003
+ return newCanvas;
3004
+ }
3005
+ }
3006
+
3007
+ return canvas;
3008
+ };
3009
+ };
3010
+ })(window,document);