├── soc.jpeg ├── README.md ├── save.php ├── js ├── jquery.plugin.html2canvas.js └── html2canvas.js └── index.php /soc.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kublaios/html2canvas-basic/HEAD/soc.jpeg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | html2canvas-basic 2 | ================= 3 | 4 | Basic usage for html2canvas library with Javascript and PHP. 5 | 6 | html2canvas official page: http://html2canvas.hertzen.com/ 7 | 8 | This sample implements how to use html2canvas for capturing a div and save it to server with PHP code. 9 | 10 | Documentation and live demo can be found here: http://www.kubilayerdogan.net/?p=304 -------------------------------------------------------------------------------- /save.php: -------------------------------------------------------------------------------- 1 | 11 |

Save the image and show to user

12 | 13 | 14 | 18 | 22 | 23 | 24 | 36 | 37 |
15 | 16 | Click Here to See The Image Saved to Server 17 | 19 | 20 | Click Here to Go Back 21 |
25 |
26 |
27 | 28 | Here is Client-sided image: 29 | 30 |
31 | '; 34 | ?> 35 |
38 | -------------------------------------------------------------------------------- /js/jquery.plugin.html2canvas.js: -------------------------------------------------------------------------------- 1 | /** 2 | @license html2canvas v0.34 3 | Copyright (c) 2011 Niklas von Hertzen. All rights reserved. 4 | http://www.twitter.com/niklasvh 5 | 6 | Released under MIT License 7 | */ 8 | /* 9 | * jQuery helper plugin for examples and tests 10 | */ 11 | (function( $ ){ 12 | $.fn.html2canvas = function(options) { 13 | if (options && options.profile && window.console && window.console.profile) { 14 | console.profile(); 15 | } 16 | var date = new Date(), 17 | html2obj, 18 | $message = null, 19 | timeoutTimer = false, 20 | timer = date.getTime(); 21 | options = options || {}; 22 | 23 | options.onrendered = options.onrendered || function( canvas ) { 24 | var $canvas = $(canvas), 25 | finishTime = new Date(); 26 | 27 | if (options && options.profile && window.console && window.console.profileEnd) { 28 | console.profileEnd(); 29 | } 30 | $canvas.css({ 31 | position: 'absolute', 32 | left: 0, 33 | top: 0 34 | }).appendTo(document.body); 35 | $canvas.siblings().toggle(); 36 | 37 | $(window).click(function(){ 38 | $canvas.toggle().siblings().toggle(); 39 | throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden")); 40 | }); 41 | throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms
",4000); 42 | 43 | // test if canvas is read-able 44 | try { 45 | $canvas[0].toDataURL(); 46 | } catch(e) { 47 | if ($canvas[0].nodeName.toLowerCase() === "canvas") { 48 | // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed 49 | alert("Canvas is tainted, unable to read data"); 50 | } 51 | } 52 | }; 53 | 54 | html2obj = html2canvas(this, options); 55 | 56 | function throwMessage(msg,duration){ 57 | window.clearTimeout(timeoutTimer); 58 | timeoutTimer = window.setTimeout(function(){ 59 | $message.fadeOut(function(){ 60 | $message.remove(); 61 | $message = null; 62 | }); 63 | },duration || 2000); 64 | if ($message) 65 | $message.remove(); 66 | $message = $('
').html(msg).css({ 67 | margin:0, 68 | padding:10, 69 | background: "#000", 70 | opacity:0.7, 71 | position:"fixed", 72 | top:10, 73 | right:10, 74 | fontFamily: 'Tahoma', 75 | color:'#fff', 76 | fontSize:12, 77 | borderRadius:12, 78 | width:'auto', 79 | height:'auto', 80 | textAlign:'center', 81 | textDecoration:'none', 82 | display:'none' 83 | }).appendTo(document.body).fadeIn(); 84 | html2obj.log(msg); 85 | } 86 | }; 87 | })( jQuery ); 88 | 89 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Simple Implementation of html2canvas With JavaScript and PHP

6 | 7 |
8 | 9 |
10 | 11 | 12 | 25 | 26 | 27 | 30 | 88 | 89 |
13 | 14 | 15 | 18 | 22 | 23 |
16 | 17 | 19 | 20 | Documentation (Back to Site) 21 |
24 |
28 | Div: 29 | 31 |
32 | 33 | 34 | 37 | 38 | 39 | 42 | 45 | 46 | 47 | 50 | 54 | 55 | 56 | 59 | 76 | 77 | 78 | 81 | 84 | 85 |
35 | This is sample implementation 36 |
40 | It can hold form values: 41 | 43 | 44 |
48 | Simple Button Element: 49 | 51 | 53 |
57 | Let's go with CSS: 58 | 60 |
61 |

Aside heading

62 |

Duis autem vel eum iriure dolor in hendrerit!

63 |
64 |
65 |

My first styled page

66 |

Welcome to my styled page!

67 |

It lacks images, but at least it has style.

68 |

There should be more here, but I don't know what yet.

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