├── .gitignore ├── example ├── img │ ├── 01.jpg │ ├── 02.jpg │ ├── 03.jpg │ ├── 04.jpg │ ├── 05.jpg │ ├── 06.jpg │ ├── 07.jpg │ ├── 08.jpg │ ├── 09.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ └── 16.jpg ├── example.js ├── canvas │ ├── index.html │ ├── canvas.css │ └── canvas.js ├── example.css └── index.html ├── server.js ├── README.md ├── LICENSE ├── src ├── genie.css ├── jquery.genie.js └── genie.js └── lib └── html2canvas.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /example/img/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/01.jpg -------------------------------------------------------------------------------- /example/img/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/02.jpg -------------------------------------------------------------------------------- /example/img/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/03.jpg -------------------------------------------------------------------------------- /example/img/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/04.jpg -------------------------------------------------------------------------------- /example/img/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/05.jpg -------------------------------------------------------------------------------- /example/img/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/06.jpg -------------------------------------------------------------------------------- /example/img/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/07.jpg -------------------------------------------------------------------------------- /example/img/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/08.jpg -------------------------------------------------------------------------------- /example/img/09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/09.jpg -------------------------------------------------------------------------------- /example/img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/10.jpg -------------------------------------------------------------------------------- /example/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/11.jpg -------------------------------------------------------------------------------- /example/img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/12.jpg -------------------------------------------------------------------------------- /example/img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/13.jpg -------------------------------------------------------------------------------- /example/img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/14.jpg -------------------------------------------------------------------------------- /example/img/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/15.jpg -------------------------------------------------------------------------------- /example/img/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamilkp/geniejs/HEAD/example/img/16.jpg -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var express = require('express'), 3 | app = express(); 4 | 5 | //server configuration 6 | app.use(express.bodyParser()); 7 | app.use(express.static(__dirname)); 8 | 9 | //start 10 | app.listen(30303); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GenieJS 2 | ======= 3 | 4 | Genie effect in a browser. The genie.js library doesn't require any additional libraries (e.g. jQuery to work). However a conveniance jQuery plugin is also included that wraps calls to genieJS. In order to perform genie effect transitions on HTML elements (not just images) the html2canvas.js library is also required. -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(fn, undefined){ 2 | var lastExpandedElement, 3 | lastExpandedDir, 4 | callbackFn = function(){ 5 | console.log('%cgenie animation finished', "color: teal"); 6 | }; 7 | $(document).on('click', function(ev){ 8 | var target = $(ev.target); 9 | if (target.hasClass('genie-thumb')) { 10 | var dir = /dock-\S+(\s|$)/.exec(target.parent().parent()[0].className)[0].slice(5); 11 | lastExpandedElement = target; 12 | lastExpandedDir = dir; 13 | target.genieExpand($('#genie-target'), [dir], null, callbackFn); 14 | } 15 | else if(target.hasClass('genie')){ 16 | if(!!lastExpandedElement) 17 | target.genieCollapse(lastExpandedElement, [lastExpandedDir], null, callbackFn); 18 | lastExpandedElement = undefined; 19 | lastExpandedDir = undefined; 20 | } 21 | }); 22 | }); -------------------------------------------------------------------------------- /example/canvas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Genie with html2canvas 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kamil Pekala (kamilkp@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /example/canvas/canvas.css: -------------------------------------------------------------------------------- 1 | [genie-source]{ 2 | position: absolute; 3 | width: 50px; 4 | height: 50px; 5 | background-color: rgba(255, 0, 0, 0.4); 6 | cursor: pointer; 7 | z-index: 10; 8 | } 9 | .left{ 10 | left: 20px; 11 | top: 50%; 12 | } 13 | .top{ 14 | left: 50%; 15 | top: 20px; 16 | } 17 | .right{ 18 | right: 20px; 19 | top: 50%; 20 | } 21 | .bottom{ 22 | left: 50%; 23 | bottom: 20px; 24 | } 25 | #genie-target-wrapper{ 26 | position: absolute; 27 | top: 80px; 28 | display: block; 29 | text-align: center; 30 | width: 100%; 31 | } 32 | #genie-target{ 33 | position: relative; 34 | display: inline-block; 35 | height: 641px; 36 | width: 530px; 37 | border: 1px solid black; 38 | text-align: left; 39 | } 40 | #temp-wrapper{ 41 | position: absolute; 42 | opacity: 0; 43 | pointer-events: none; 44 | } 45 | .setup-header, .setup-wrapper{ 46 | width: 479px !important; 47 | } 48 | .steps{ 49 | margin: 10px auto 0 !important; 50 | width: 719px !important; 51 | } 52 | .site.clearfix{ 53 | display: inline-block !important; 54 | width: 530px !important; 55 | } 56 | .setup-wrapper{ 57 | padding-top: 0 !important; 58 | } 59 | #site-container>.container:first-child { 60 | margin-top: 0 !important; 61 | } -------------------------------------------------------------------------------- /example/example.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow: hidden; 3 | } 4 | .dock { 5 | position: absolute; 6 | text-align: center; 7 | list-style-type: none; 8 | padding: 0px; 9 | margin: 0px; 10 | z-index: 1; 11 | } 12 | .dock-top, .dock-bottom{ 13 | min-width: 1000px; 14 | } 15 | .dock-top{ 16 | top: 10px; 17 | left: 0px; 18 | right: 0px; 19 | } 20 | .dock-right{ 21 | right: 10px; 22 | top: 0; 23 | bottom: 0; 24 | } 25 | .dock-bottom{ 26 | bottom: 10px; 27 | left: 0px; 28 | right: 0px; 29 | } 30 | .dock-left{ 31 | left: 10px; 32 | top: 0; 33 | bottom: 0; 34 | } 35 | .dock-top li, .dock-bottom li{ 36 | display: inline-block; 37 | } 38 | .dock div.genie-thumb{ 39 | background-repeat: no-repeat; 40 | background-size: 100%; 41 | width: 80px; 42 | height: 60px; 43 | cursor: pointer; 44 | border: 1px solid black; 45 | } 46 | .dock-top li, .dock-bottom li{ 47 | margin: 20px 50px; 48 | } 49 | .dock-left li, .dock-right li{ 50 | margin: 100px 20px; 51 | } 52 | .dock div.genie-thumb.genie-thumb { 53 | -moz-transition: background-position 800ms ease-in-out; 54 | -webkit-transition: background-position 800ms ease-in-out; 55 | -o-transition: background-position 800ms ease-in-out; 56 | -ms-transition: background-position 800ms ease-in-out; 57 | transition: background-position 800ms ease-in-out; 58 | } 59 | #genie-target-wrapper{ 60 | position: absolute; 61 | text-align: center; 62 | top: 20%; 63 | cursor: pointer; 64 | width: 100%; 65 | } 66 | #genie-target{ 67 | display: inline-block; 68 | border: 1px solid black; 69 | width: 800px; 70 | height: 600px; 71 | } 72 | #jet-photos-ref{ 73 | position: absolute; 74 | z-index: 99999; 75 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Genie Effect 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Photographs originally found at jetphotos.net 15 |
16 | 22 | 28 | 34 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /src/genie.css: -------------------------------------------------------------------------------- 1 | .genie { 2 | position: relative; 3 | background-repeat: no-repeat; 4 | background-size: cover; 5 | } 6 | .genie .genie-step { 7 | background-repeat: no-repeat; 8 | background-image: inherit; 9 | position: absolute; 10 | } 11 | /* fast */ 12 | .genie.expand .genie-step { 13 | -moz-transition: background-position 800ms ease-in-out; 14 | -webkit-transition: background-position 800ms ease-in-out; 15 | -o-transition: background-position 800ms ease-in-out; 16 | -ms-transition: background-position 800ms ease-in-out; 17 | transition: background-position 800ms ease-in-out; 18 | } 19 | .genie.collapse .genie-step { 20 | -moz-transition: all 300ms ease-in-out; 21 | -webkit-transition: all 300ms ease-in-out; 22 | -o-transition: all 300ms ease-in-out; 23 | -ms-transition: all 300ms ease-in-out; 24 | transition: all 300ms ease-in-out; 25 | } 26 | .genie.collapse.change-pace .genie-step { 27 | -moz-transition: background-position 800ms ease-in-out; 28 | -webkit-transition: background-position 800ms ease-in-out; 29 | -o-transition: background-position 800ms ease-in-out; 30 | -ms-transition: background-position 800ms ease-in-out; 31 | transition: background-position 800ms ease-in-out; 32 | } 33 | .genie.expand.change-pace .genie-step { 34 | -moz-transition: all 300ms ease-in-out; 35 | -webkit-transition: all 300ms ease-in-out; 36 | -o-transition: all 300ms ease-in-out; 37 | -ms-transition: all 300ms ease-in-out; 38 | transition: all 300ms ease-in-out; 39 | } 40 | 41 | /* slow */ 42 | /*.genie.expand .genie-step { 43 | -moz-transition: background-position 2000ms ease-in-out; 44 | -webkit-transition: background-position 2000ms ease-in-out; 45 | -o-transition: background-position 2000ms ease-in-out; 46 | -ms-transition: background-position 2000ms ease-in-out; 47 | transition: background-position 2000ms ease-in-out; 48 | } 49 | .genie.collapse .genie-step { 50 | -moz-transition: all 800ms ease-in-out; 51 | -webkit-transition: all 800ms ease-in-out; 52 | -o-transition: all 800ms ease-in-out; 53 | -ms-transition: all 800ms ease-in-out; 54 | transition: all 800ms ease-in-out; 55 | } 56 | .genie.collapse.change-pace .genie-step { 57 | -moz-transition: background-position 2000ms ease-in-out; 58 | -webkit-transition: background-position 2000ms ease-in-out; 59 | -o-transition: background-position 2000ms ease-in-out; 60 | -ms-transition: background-position 2000ms ease-in-out; 61 | transition: background-position 2000ms ease-in-out; 62 | } 63 | .genie.expand.change-pace .genie-step { 64 | -moz-transition: all 800ms ease-in-out; 65 | -webkit-transition: all 800ms ease-in-out; 66 | -o-transition: all 800ms ease-in-out; 67 | -ms-transition: all 800ms ease-in-out; 68 | transition: all 800ms ease-in-out; 69 | }*/ 70 | -------------------------------------------------------------------------------- /src/jquery.genie.js: -------------------------------------------------------------------------------- 1 | 2 | // Javascript jQuery plugin for Genie Effect animations by Kamil Pękala 2013 3 | 4 | (function($, window, document, undefined){ 5 | // 'use strict'; 6 | 7 | $.fn.genieCollapse = function(target, directions, step_quantum, callback){ 8 | window.genie.collapse(this[0], target[0], directions, step_quantum, callback); 9 | return this; 10 | }; 11 | 12 | $.fn.genieExpand = function(target, directions, step_quantum, callback){ 13 | window.genie.expand(this[0], target[0], directions, step_quantum, callback); 14 | return this; 15 | }; 16 | 17 | $.fn.htmlGenieCollapse = function(target, directions, step_quantum, callback){ 18 | var src = $(this); 19 | if(typeof step_quantum === "function"){ 20 | callback = step_quantum; 21 | step_quantum = null; 22 | } 23 | html2canvas(src[0], { 24 | onrendered: function(canvas){ 25 | var png = canvas.toDataURL("image/png"), 26 | width = src.outerWidth(), 27 | height = src.outerHeight(), 28 | top = src.position().top, 29 | img = $('
'), 30 | endCallback = function(){ 31 | $(this).remove(); 32 | if(typeof callback === "function") 33 | callback.call(src); 34 | }; 35 | img.css({ 36 | 'height': height + 'px', 37 | 'width': width + 'px', 38 | 'background-image': "url('" + png + "')", 39 | 'background-position': '0px 0px', 40 | 'position': 'absolute', 41 | 'top': top, 42 | 'display': 'block' 43 | }); 44 | src.replaceWith(img); 45 | img.genieCollapse(target, directions, step_quantum, endCallback); 46 | } 47 | }); 48 | return this; 49 | }; 50 | 51 | $.fn.htmlGenieExpand = function(target, element, directions, step_quantum, callback){ 52 | var src = $(this); 53 | if(typeof step_quantum === "function"){ 54 | callback = step_quantum; 55 | step_quantum = undefined; 56 | } 57 | html2canvas(element[0], { 58 | onrendered: function(canvas){ 59 | var png = canvas.toDataURL("image/png"), 60 | width = element.outerWidth(), 61 | height = element.outerHeight(), 62 | img = $('
'), 63 | endCallback = function(){ 64 | img.remove(); 65 | target.css({ 66 | 'background-image': '', 67 | 'background-position': '' 68 | }); 69 | target[0].innerHTML = element.clone().html(); 70 | if(typeof callback === "function"){ 71 | callback.call(target); 72 | } 73 | }; 74 | console.log(png); 75 | img.css({ 76 | 'height': 'inherit', 77 | 'width': 'inherit', 78 | 'background-image': "url('" + png + "')", 79 | 'background-position': '0px 0px', 80 | }); 81 | target.empty(); 82 | src.append(img); 83 | img.genieExpand(target, directions, step_quantum, endCallback); 84 | } 85 | }); 86 | return this; 87 | }; 88 | 89 | $.fn.urlGenieExpand = function(target, url, directions, step_quantum, callback){ 90 | var src = $(this); 91 | if(typeof step_quantum === "function"){ 92 | callback = step_quantum; 93 | step_quantum = undefined; 94 | } 95 | var img = $('
'); 96 | img.css({ 97 | 'height': 'inherit', 98 | 'width': 'inherit', 99 | 'background-image': "url('" + url + "')", 100 | 'background-position': '0px 0px', 101 | }); 102 | target.empty(); 103 | src.append(img); 104 | img.genieExpand(target, directions, step_quantum, function(){ 105 | if(typeof callback === "function"){ 106 | callback.call(target); 107 | } 108 | }); 109 | 110 | return this; 111 | }; 112 | })(jQuery, window, document); -------------------------------------------------------------------------------- /example/canvas/canvas.js: -------------------------------------------------------------------------------- 1 | var elementHtml = '

Join GitHub

The best way to design, build, and ship software.

  1. Step 1: Set up a personal account
  2. Step 2: Choose your plan
  3. Step 3: Go to your dashboard
'; 2 | $(document).ready(function(fn, undefined){ 3 | $('#temp-wrapper-content').append(elementHtml); 4 | $('#genie-target').css({ 5 | width: $('#temp-wrapper-content').outerWidth(), 6 | height: $('#temp-wrapper-content').outerHeight() 7 | }) 8 | $('[genie-source]').click(function(){ 9 | $(this).htmlGenieExpand($('#genie-target'), $('#temp-wrapper-content'), 10 | [this.className]); 11 | }); 12 | }); -------------------------------------------------------------------------------- /src/genie.js: -------------------------------------------------------------------------------- 1 | 2 | // Javascript library for Genie Effect animations by Kamil Pękala 2013 3 | 4 | (function(window, undefined){ 5 | 'use strict'; 6 | 7 | var document = window.document, 8 | getClientDimensions = function(el){ 9 | if (!el) return null; 10 | var rect = el.getBoundingClientRect(); 11 | return { 12 | w: rect.width, 13 | h: rect.height, 14 | t: rect.top, 15 | l: rect.left, 16 | b: rect.bottom, 17 | r: rect.right, 18 | obj: el 19 | }; 20 | }, 21 | prefixedEvent = function(el, type, callback, capture) { 22 | var pfx = 'webkit moz ms o '.split(' '); 23 | for (var p=0, pl=pfx.length; p=0; i--) { 72 | pystr = step[i].style.backgroundPosition.split(' ')[1]; 73 | py = parseFloat(pystr.slice(0, pystr.length - 2), 10); 74 | step[i].style.backgroundPosition = '0px '+ (py - diffB) +'px'; 75 | } 76 | break; 77 | case 'bottom': 78 | for (i=0; i=0; i--) { 86 | pxstr = step[i].style.backgroundPosition.split(' ')[0]; 87 | px = parseFloat(pxstr.slice(0, pxstr.length - 2), 10); 88 | step[i].style.backgroundPosition = (px - diffR) + 'px 0px'; 89 | } 90 | break; 91 | case 'right': 92 | for (i=0; i= 0 && step_length_bottom > step_length_max){ 167 | step_length_max = step_length_bottom; 168 | this.animationDirection = 'bottom'; 169 | } 170 | if(this.possibleDirections.indexOf('top') >= 0 && step_length_top > step_length_max){ 171 | step_length_max = step_length_top; 172 | this.animationDirection = 'top'; 173 | } 174 | if(this.possibleDirections.indexOf('left') >= 0 && step_length_left > step_length_max){ 175 | step_length_max = step_length_left; 176 | this.animationDirection = 'left'; 177 | } 178 | if(this.possibleDirections.indexOf('right') >= 0 && step_length_right > step_length_max){ 179 | step_length_max = step_length_right; 180 | this.animationDirection = 'right'; 181 | } 182 | 183 | switch(this.animationDirection){ 184 | case 'top': 185 | var off = Math.ceil((source_dim.t - target_dim.b) / step_quantum); 186 | fixed.style.width = source_dim.w + 'px'; 187 | fixed.style.height = source_dim.h + 'px'; 188 | fixed.style.top = source_dim.t + 'px'; 189 | fixed.style.left = source_dim.l + 'px'; 190 | fixed.style.position = 'absolute'; 191 | fixed.style.backgroundPosition = source.style.backgroundPosition; 192 | fixed.style.backgroundImage = source.style.backgroundImage; 193 | fixed.style.display = source.style.display; 194 | 195 | fixed.style.backgroundPosition = '0px 9999px'; 196 | 197 | for (i=-off; i'; 202 | } 203 | fixed.innerHTML = htm; 204 | 205 | setTimeout(function() { 206 | source.style.backgroundPosition = '0px 9999px'; 207 | var steps = fixed.childNodes, 208 | radians_left = Math.floor((target_dim.l - source_dim.l) / 2), 209 | radians_width = Math.floor((target_dim.w - source_dim.w) / 2), 210 | rw_offset = radians_width - target_dim.w + 1, 211 | increase = (Math.PI * 2) / (step_length_top * 2), 212 | counter = 4.7, 213 | i = 0, 214 | il = steps.length; 215 | for (; i'; 241 | } 242 | fixed.innerHTML = htm; 243 | 244 | setTimeout(function() { 245 | source.style.backgroundPosition = '0px -9999px'; 246 | var steps = fixed.childNodes, 247 | radians_left = Math.floor((target_dim.l - source_dim.l) / 2), 248 | radians_width = Math.floor((target_dim.w - source_dim.w) / 2), 249 | rw_offset = radians_width - target_dim.w + 1, 250 | increase = (Math.PI * 2) / (step_length_bottom * 2), 251 | counter = 4.7, 252 | i = 0, 253 | il = steps.length; 254 | for (; i'; 280 | } 281 | fixed.innerHTML = htm; 282 | setTimeout(function() { 283 | source.style.backgroundPosition = '-9999px 0px'; 284 | var steps = fixed.childNodes, 285 | radians_top = Math.floor((target_dim.t - source_dim.t) / 2), 286 | radians_height = Math.floor((target_dim.h - source_dim.h) / 2), 287 | rh_offset = radians_height - target_dim.h + 1, 288 | increase = (Math.PI * 2) / (step_length_right * 2), 289 | counter = 4.7, 290 | i = 0, 291 | il = steps.length; 292 | for (; i'; 319 | } 320 | fixed.innerHTML = htm; 321 | setTimeout(function() { 322 | var steps = fixed.childNodes, 323 | radians_top = Math.floor((target_dim.t - source_dim.t) / 2), 324 | radians_height = Math.floor((target_dim.h - source_dim.h) / 2), 325 | rh_offset = radians_height - target_dim.h + 1, 326 | increase = (Math.PI * 2) / (step_length_left * 2), 327 | counter = 4.7, 328 | i = 0, 329 | il = steps.length; 330 | for (; i= 0 && step_length_bottom > step_length_max){ 373 | step_length_max = step_length_bottom; 374 | this.animationDirection = 'bottom'; 375 | } 376 | if(this.possibleDirections.indexOf('top') >= 0 && step_length_top > step_length_max){ 377 | step_length_max = step_length_top; 378 | this.animationDirection = 'top'; 379 | } 380 | if(this.possibleDirections.indexOf('left') >= 0 && step_length_left > step_length_max){ 381 | step_length_max = step_length_left; 382 | this.animationDirection = 'left'; 383 | } 384 | if(this.possibleDirections.indexOf('right') >= 0 && step_length_right > step_length_max){ 385 | step_length_max = step_length_right; 386 | this.animationDirection = 'right'; 387 | } 388 | switch(this.animationDirection){ 389 | case 'bottom': 390 | var diffT = source_dim.t - target_dim.t, 391 | radians_left = Math.floor((source_dim.l - target_dim.l) / 2), 392 | radians_width = Math.floor((source_dim.w - target_dim.w) / 2), 393 | rw_offset = radians_width - source_dim.w + 1, 394 | increase = (Math.PI * 2) / (step_length_bottom * 2); 395 | 396 | fixed.style.width = target_dim.w + 'px'; 397 | fixed.style.height = target_dim.h + 'px'; 398 | fixed.style.top = target_dim.t + 'px'; 399 | fixed.style.left = target_dim.l + 'px'; 400 | fixed.style.position = 'absolute'; 401 | fixed.style.backgroundPosition = target.style.backgroundPosition; 402 | fixed.style.backgroundImage = target.style.backgroundImage; 403 | fixed.style.display = target.style.display; 404 | fixed.className = "genie"; 405 | 406 | for (; i'; 412 | counter += increase; 413 | } 414 | fixed.innerHTML = htm; 415 | 416 | prefixedEvent(fixed.childNodes[0], 'TransitionEnd', delegate.call(this, this.expandTransitionEvent)); 417 | 418 | setTimeout(function() { 419 | var steps = fixed.childNodes, 420 | pystr, 421 | py, 422 | i=0, 423 | il=steps.length; 424 | for (; i'; 458 | counter += increase; 459 | } 460 | fixed.innerHTML = htm; 461 | 462 | prefixedEvent(fixed.childNodes[0], 'TransitionEnd', delegate.call(this, this.expandTransitionEvent)); 463 | 464 | setTimeout(function() { 465 | var steps = fixed.childNodes, 466 | pystr, 467 | py, 468 | i=0, 469 | il=steps.length; 470 | for (; i'; 502 | counter += increase; 503 | } 504 | fixed.innerHTML = htm; 505 | 506 | prefixedEvent(fixed.childNodes[0], 'TransitionEnd', delegate.call(this, this.expandTransitionEvent)); 507 | 508 | setTimeout(function() { 509 | var steps = fixed.childNodes, 510 | pxstr, 511 | px, 512 | i=0, 513 | il=steps.length; 514 | for (; i'; 548 | counter += increase; 549 | } 550 | fixed.innerHTML = htm; 551 | 552 | prefixedEvent(fixed.childNodes[0], 'TransitionEnd', delegate.call(this, this.expandTransitionEvent)); 553 | 554 | setTimeout(function() { 555 | var steps = fixed.childNodes, 556 | pxstr, 557 | px, 558 | i=0, 559 | il=steps.length; 560 | for (; i 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 -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 :" + 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 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 | "", 2609 | "", 2610 | "
", 2611 | "sup", 2612 | "
", 2613 | "
", 2614 | "
" 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); --------------------------------------------------------------------------------