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