| @@ -0,0 +1,3010 @@ |
| @@ -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); |