watir-screenshot-stitch 0.6.6 → 0.6.7

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