├── .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 |
21 |
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 |
16 |
22 |
28 |
34 |
40 |
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 = ' ';
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 | ""
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);
--------------------------------------------------------------------------------