2013-02-28 15:00:04 +07:00
/ *
html2canvas 0.4 . 0 < http : //html2canvas.hertzen.com>
Copyright ( c ) 2013 Niklas von Hertzen ( @ niklasvh )
Released under MIT License
* /
( function ( window , document , undefined ) {
"use strict" ;
var _html2canvas = { } ,
previousElement ,
computedCSS ,
html2canvas ;
function h2clog ( a ) {
if ( _html2canvas . logging && window . console && window . console . log ) {
window . console . log ( a ) ;
}
}
_html2canvas . Util = { } ;
_html2canvas . Util . trimText = ( function ( isNative ) {
return function ( input ) {
if ( isNative ) { return isNative . apply ( input ) ; }
else { return ( ( input || '' ) + '' ) . replace ( /^\s+|\s+$/g , '' ) ; }
} ;
} ) ( String . prototype . trim ) ;
_html2canvas . Util . parseBackgroundImage = function ( value ) {
var whitespace = ' \r\n\t' ,
method , definition , prefix , prefix _i , block , results = [ ] ,
c , mode = 0 , numParen = 0 , quote , args ;
var appendResult = function ( ) {
if ( method ) {
if ( definition . substr ( 0 , 1 ) === '"' ) {
definition = definition . substr ( 1 , definition . length - 2 ) ;
}
if ( definition ) {
args . push ( definition ) ;
}
if ( method . substr ( 0 , 1 ) === '-' &&
( prefix _i = method . indexOf ( '-' , 1 ) + 1 ) > 0 ) {
prefix = method . substr ( 0 , prefix _i ) ;
method = method . substr ( prefix _i ) ;
}
results . push ( {
prefix : prefix ,
method : method . toLowerCase ( ) ,
value : block ,
args : args
} ) ;
}
args = [ ] ; //for some odd reason, setting .length = 0 didn't work in safari
method =
prefix =
definition =
block = '' ;
} ;
appendResult ( ) ;
if ( typeof value == "undefined" ) {
return ;
}
for ( var i = 0 , ii = value . length ; i < ii ; i ++ ) {
c = value [ i ] ;
if ( mode === 0 && whitespace . indexOf ( c ) > - 1 ) {
continue ;
}
switch ( c ) {
case '"' :
if ( ! quote ) {
quote = c ;
}
else if ( quote === c ) {
quote = null ;
}
break ;
case '(' :
if ( quote ) { break ; }
else if ( mode === 0 ) {
mode = 1 ;
block += c ;
continue ;
} else {
numParen ++ ;
}
break ;
case ')' :
if ( quote ) { break ; }
else if ( mode === 1 ) {
if ( numParen === 0 ) {
mode = 0 ;
block += c ;
appendResult ( ) ;
continue ;
} else {
numParen -- ;
}
}
break ;
case ',' :
if ( quote ) { break ; }
else if ( mode === 0 ) {
appendResult ( ) ;
continue ;
}
else if ( mode === 1 ) {
if ( numParen === 0 && ! method . match ( /^url$/i ) ) {
args . push ( definition ) ;
definition = '' ;
block += c ;
continue ;
}
}
break ;
}
block += c ;
if ( mode === 0 ) { method += c ; }
else { definition += c ; }
}
appendResult ( ) ;
return results ;
} ;
_html2canvas . Util . Bounds = function getBounds ( el ) {
var clientRect ,
bounds = { } ;
if ( el . getBoundingClientRect ) {
clientRect = el . getBoundingClientRect ( ) ;
// TODO add scroll position to bounds, so no scrolling of window necessary
bounds . top = clientRect . top ;
bounds . bottom = clientRect . bottom || ( clientRect . top + clientRect . height ) ;
bounds . left = clientRect . left ;
// older IE doesn't have width/height, but top/bottom instead
bounds . width = clientRect . width || ( clientRect . right - clientRect . left ) ;
bounds . height = clientRect . height || ( clientRect . bottom - clientRect . top ) ;
return bounds ;
}
} ;
_html2canvas . Util . getCSS = function ( el , attribute , index ) {
// return $(el).css(attribute);
var val ,
isBackgroundSizePosition = attribute . match ( /^background(Size|Position)$/ ) ;
function toPX ( attribute , val ) {
var rsLeft = el . runtimeStyle && el . runtimeStyle [ attribute ] ,
left ,
style = el . style ;
// Check if we are not dealing with pixels, (Opera has issues with this)
// Ported from jQuery css.js
// From the awesome hack by Dean Edwards
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to pixels
if ( ! /^-?[0-9]+\.?[0-9]*(?:px)?$/i . test ( val ) && /^-?\d/ . test ( val ) ) {
// Remember the original values
left = style . left ;
// Put in the new values to get a computed value out
if ( rsLeft ) {
el . runtimeStyle . left = el . currentStyle . left ;
}
style . left = attribute === "fontSize" ? "1em" : ( val || 0 ) ;
val = style . pixelLeft + "px" ;
// Revert the changed values
style . left = left ;
if ( rsLeft ) {
el . runtimeStyle . left = rsLeft ;
}
}
if ( ! /^(thin|medium|thick)$/i . test ( val ) ) {
return Math . round ( parseFloat ( val ) ) + "px" ;
}
return val ;
}
if ( previousElement !== el ) {
computedCSS = document . defaultView . getComputedStyle ( el , null ) ;
}
val = computedCSS [ attribute ] ;
if ( isBackgroundSizePosition ) {
val = ( val || '' ) . split ( ',' ) ;
val = val [ index || 0 ] || val [ 0 ] || 'auto' ;
val = _html2canvas . Util . trimText ( val ) . split ( ' ' ) ;
if ( attribute === 'backgroundSize' && ( ! val [ 0 ] || val [ 0 ] . match ( /cover|contain|auto/ ) ) ) {
//these values will be handled in the parent function
} else {
val [ 0 ] = ( val [ 0 ] . indexOf ( "%" ) === - 1 ) ? toPX ( attribute + "X" , val [ 0 ] ) : val [ 0 ] ;
if ( val [ 1 ] === undefined ) {
if ( attribute === 'backgroundSize' ) {
val [ 1 ] = 'auto' ;
return val ;
}
else {
// IE 9 doesn't return double digit always
val [ 1 ] = val [ 0 ] ;
}
}
val [ 1 ] = ( val [ 1 ] . indexOf ( "%" ) === - 1 ) ? toPX ( attribute + "Y" , val [ 1 ] ) : val [ 1 ] ;
}
} else if ( /border(Top|Bottom)(Left|Right)Radius/ . test ( attribute ) ) {
var arr = val . split ( " " ) ;
if ( arr . length <= 1 ) {
arr [ 1 ] = arr [ 0 ] ;
}
arr [ 0 ] = parseInt ( arr [ 0 ] , 10 ) ;
arr [ 1 ] = parseInt ( arr [ 1 ] , 10 ) ;
val = arr ;
}
return val ;
} ;
_html2canvas . Util . resizeBounds = function ( current _width , current _height , target _width , target _height , stretch _mode ) {
var target _ratio = target _width / target _height ,
current _ratio = current _width / current _height ,
output _width , output _height ;
if ( ! stretch _mode || stretch _mode === 'auto' ) {
output _width = target _width ;
output _height = target _height ;
} else {
if ( target _ratio < current _ratio ^ stretch _mode === 'contain' ) {
output _height = target _height ;
output _width = target _height * current _ratio ;
} else {
output _width = target _width ;
output _height = target _width / current _ratio ;
}
}
return { width : output _width , height : output _height } ;
} ;
function backgroundBoundsFactory ( prop , el , bounds , image , imageIndex , backgroundSize ) {
var bgposition = _html2canvas . Util . getCSS ( el , prop , imageIndex ) ,
topPos ,
left ,
percentage ,
val ;
if ( bgposition . length === 1 ) {
val = bgposition [ 0 ] ;
bgposition = [ ] ;
bgposition [ 0 ] = val ;
bgposition [ 1 ] = val ;
}
if ( bgposition [ 0 ] . toString ( ) . indexOf ( "%" ) !== - 1 ) {
percentage = ( parseFloat ( bgposition [ 0 ] ) / 100 ) ;
left = bounds . width * percentage ;
if ( prop !== 'backgroundSize' ) {
left -= ( backgroundSize || image ) . width * percentage ;
}
} else {
if ( prop === 'backgroundSize' ) {
if ( bgposition [ 0 ] === 'auto' ) {
left = image . width ;
} else {
if ( bgposition [ 0 ] . match ( /contain|cover/ ) ) {
var resized = _html2canvas . Util . resizeBounds ( image . width , image . height , bounds . width , bounds . height , bgposition [ 0 ] ) ;
left = resized . width ;
topPos = resized . height ;
} else {
left = parseInt ( bgposition [ 0 ] , 10 ) ;
}
}
} else {
left = parseInt ( bgposition [ 0 ] , 10 ) ;
}
}
if ( bgposition [ 1 ] === 'auto' ) {
topPos = left / image . width * image . height ;
} else if ( bgposition [ 1 ] . toString ( ) . indexOf ( "%" ) !== - 1 ) {
percentage = ( parseFloat ( bgposition [ 1 ] ) / 100 ) ;
topPos = bounds . height * percentage ;
if ( prop !== 'backgroundSize' ) {
topPos -= ( backgroundSize || image ) . height * percentage ;
}
} else {
topPos = parseInt ( bgposition [ 1 ] , 10 ) ;
}
return [ left , topPos ] ;
}
_html2canvas . Util . BackgroundPosition = function ( el , bounds , image , imageIndex , backgroundSize ) {
var result = backgroundBoundsFactory ( 'backgroundPosition' , el , bounds , image , imageIndex , backgroundSize ) ;
return { left : result [ 0 ] , top : result [ 1 ] } ;
} ;
_html2canvas . Util . BackgroundSize = function ( el , bounds , image , imageIndex ) {
var result = backgroundBoundsFactory ( 'backgroundSize' , el , bounds , image , imageIndex ) ;
return { width : result [ 0 ] , height : result [ 1 ] } ;
} ;
_html2canvas . Util . Extend = function ( options , defaults ) {
for ( var key in options ) {
if ( options . hasOwnProperty ( key ) ) {
defaults [ key ] = options [ key ] ;
}
}
return defaults ;
} ;
/ *
* Derived from jQuery . contents ( )
* Copyright 2010 , John Resig
* Dual licensed under the MIT or GPL Version 2 licenses .
* http : //jquery.org/license
* /
_html2canvas . Util . Children = function ( elem ) {
var children ;
try {
children = ( elem . nodeName && elem . nodeName . toUpperCase ( ) === "IFRAME" ) ?
elem . contentDocument || elem . contentWindow . document : ( function ( array ) {
var ret = [ ] ;
if ( array !== null ) {
( function ( first , second ) {
var i = first . length ,
j = 0 ;
if ( typeof second . length === "number" ) {
for ( var l = second . length ; j < l ; j ++ ) {
first [ i ++ ] = second [ j ] ;
}
} else {
while ( second [ j ] !== undefined ) {
first [ i ++ ] = second [ j ++ ] ;
}
}
first . length = i ;
return first ;
} ) ( ret , array ) ;
}
return ret ;
} ) ( elem . childNodes ) ;
} catch ( ex ) {
h2clog ( "html2canvas.Util.Children failed with exception: " + ex . message ) ;
children = [ ] ;
}
return children ;
} ;
_html2canvas . Util . Font = ( function ( ) {
var fontData = { } ;
return function ( font , fontSize , doc ) {
if ( fontData [ font + "-" + fontSize ] !== undefined ) {
return fontData [ font + "-" + fontSize ] ;
}
var container = doc . createElement ( 'div' ) ,
img = doc . createElement ( 'img' ) ,
span = doc . createElement ( 'span' ) ,
sampleText = 'Hidden Text' ,
baseline ,
middle ,
metricsObj ;
container . style . visibility = "hidden" ;
container . style . fontFamily = font ;
container . style . fontSize = fontSize ;
container . style . margin = 0 ;
container . style . padding = 0 ;
doc . body . appendChild ( container ) ;
// http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
img . src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=" ;
img . width = 1 ;
img . height = 1 ;
img . style . margin = 0 ;
img . style . padding = 0 ;
img . style . verticalAlign = "baseline" ;
span . style . fontFamily = font ;
span . style . fontSize = fontSize ;
span . style . margin = 0 ;
span . style . padding = 0 ;
span . appendChild ( doc . createTextNode ( sampleText ) ) ;
container . appendChild ( span ) ;
container . appendChild ( img ) ;
baseline = ( img . offsetTop - span . offsetTop ) + 1 ;
container . removeChild ( span ) ;
container . appendChild ( doc . createTextNode ( sampleText ) ) ;
container . style . lineHeight = "normal" ;
img . style . verticalAlign = "super" ;
middle = ( img . offsetTop - container . offsetTop ) + 1 ;
metricsObj = {
baseline : baseline ,
lineWidth : 1 ,
middle : middle
} ;
fontData [ font + "-" + fontSize ] = metricsObj ;
doc . body . removeChild ( container ) ;
return metricsObj ;
} ;
} ) ( ) ;
( function ( ) {
_html2canvas . Generate = { } ;
var reGradients = [
/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/ ,
/^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/ ,
/^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/ ,
/^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/ ,
/^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/ ,
/^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/ ,
/^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
] ;
/ *
* TODO : Add IE10 vendor prefix ( - ms ) support
* TODO : Add W3C gradient ( linear - gradient ) support
* TODO : Add old Webkit - webkit - gradient ( radial , ... ) support
* TODO : Maybe some RegExp optimizations are possible ; o )
* /
_html2canvas . Generate . parseGradient = function ( css , bounds ) {
var gradient , i , len = reGradients . length , m1 , stop , m2 , m2Len , step , m3 , tl , tr , br , bl ;
for ( i = 0 ; i < len ; i += 1 ) {
m1 = css . match ( reGradients [ i ] ) ;
if ( m1 ) {
break ;
}
}
if ( m1 ) {
switch ( m1 [ 1 ] ) {
case '-webkit-linear-gradient' :
case '-o-linear-gradient' :
gradient = {
type : 'linear' ,
x0 : null ,
y0 : null ,
x1 : null ,
y1 : null ,
colorStops : [ ]
} ;
// get coordinates
m2 = m1 [ 2 ] . match ( /\w+/g ) ;
if ( m2 ) {
m2Len = m2 . length ;
for ( i = 0 ; i < m2Len ; i += 1 ) {
switch ( m2 [ i ] ) {
case 'top' :
gradient . y0 = 0 ;
gradient . y1 = bounds . height ;
break ;
case 'right' :
gradient . x0 = bounds . width ;
gradient . x1 = 0 ;
break ;
case 'bottom' :
gradient . y0 = bounds . height ;
gradient . y1 = 0 ;
break ;
case 'left' :
gradient . x0 = 0 ;
gradient . x1 = bounds . width ;
break ;
}
}
}
if ( gradient . x0 === null && gradient . x1 === null ) { // center
gradient . x0 = gradient . x1 = bounds . width / 2 ;
}
if ( gradient . y0 === null && gradient . y1 === null ) { // center
gradient . y0 = gradient . y1 = bounds . height / 2 ;
}
// get colors and stops
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 ) ;
if ( m2 ) {
m2Len = m2 . length ;
step = 1 / Math . max ( m2Len - 1 , 1 ) ;
for ( i = 0 ; i < m2Len ; i += 1 ) {
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)?/ ) ;
if ( m3 [ 2 ] ) {
stop = parseFloat ( m3 [ 2 ] ) ;
if ( m3 [ 3 ] === '%' ) {
stop /= 100 ;
} else { // px - stupid opera
stop /= bounds . width ;
}
} else {
stop = i * step ;
}
gradient . colorStops . push ( {
color : m3 [ 1 ] ,
stop : stop
} ) ;
}
}
break ;
case '-webkit-gradient' :
gradient = {
type : m1 [ 2 ] === 'radial' ? 'circle' : m1 [ 2 ] , // TODO: Add radial gradient support for older mozilla definitions
x0 : 0 ,
y0 : 0 ,
x1 : 0 ,
y1 : 0 ,
colorStops : [ ]
} ;
// get coordinates
m2 = m1 [ 3 ] . match ( /(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/ ) ;
if ( m2 ) {
gradient . x0 = ( m2 [ 1 ] * bounds . width ) / 100 ;
gradient . y0 = ( m2 [ 2 ] * bounds . height ) / 100 ;
gradient . x1 = ( m2 [ 3 ] * bounds . width ) / 100 ;
gradient . y1 = ( m2 [ 4 ] * bounds . height ) / 100 ;
}
// get colors and stops
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 ) ;
if ( m2 ) {
m2Len = m2 . length ;
for ( i = 0 ; i < m2Len ; i += 1 ) {
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\.]+)?\))\)/ ) ;
stop = parseFloat ( m3 [ 2 ] ) ;
if ( m3 [ 1 ] === 'from' ) {
stop = 0.0 ;
}
if ( m3 [ 1 ] === 'to' ) {
stop = 1.0 ;
}
gradient . colorStops . push ( {
color : m3 [ 3 ] ,
stop : stop
} ) ;
}
}
break ;
case '-moz-linear-gradient' :
gradient = {
type : 'linear' ,
x0 : 0 ,
y0 : 0 ,
x1 : 0 ,
y1 : 0 ,
colorStops : [ ]
} ;
// get coordinates
m2 = m1 [ 2 ] . match ( /(\d{1,3})%?\s(\d{1,3})%?/ ) ;
// m2[1] == 0% -> left
// m2[1] == 50% -> center
// m2[1] == 100% -> right
// m2[2] == 0% -> top
// m2[2] == 50% -> center
// m2[2] == 100% -> bottom
if ( m2 ) {
gradient . x0 = ( m2 [ 1 ] * bounds . width ) / 100 ;
gradient . y0 = ( m2 [ 2 ] * bounds . height ) / 100 ;
gradient . x1 = bounds . width - gradient . x0 ;
gradient . y1 = bounds . height - gradient . y0 ;
}
// get colors and stops
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 ) ;
if ( m2 ) {
m2Len = m2 . length ;
step = 1 / Math . max ( m2Len - 1 , 1 ) ;
for ( i = 0 ; i < m2Len ; i += 1 ) {
m3 = m2 [ i ] . match ( /((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/ ) ;
if ( m3 [ 2 ] ) {
stop = parseFloat ( m3 [ 2 ] ) ;
if ( m3 [ 3 ] ) { // percentage
stop /= 100 ;
}
} else {
stop = i * step ;
}
gradient . colorStops . push ( {
color : m3 [ 1 ] ,
stop : stop
} ) ;
}
}
break ;
case '-webkit-radial-gradient' :
case '-moz-radial-gradient' :
case '-o-radial-gradient' :
gradient = {
type : 'circle' ,
x0 : 0 ,
y0 : 0 ,
x1 : bounds . width ,
y1 : bounds . height ,
cx : 0 ,
cy : 0 ,
rx : 0 ,
ry : 0 ,
colorStops : [ ]
} ;
// center
m2 = m1 [ 2 ] . match ( /(\d{1,3})%?\s(\d{1,3})%?/ ) ;
if ( m2 ) {
gradient . cx = ( m2 [ 1 ] * bounds . width ) / 100 ;
gradient . cy = ( m2 [ 2 ] * bounds . height ) / 100 ;
}
// size
m2 = m1 [ 3 ] . match ( /\w+/ ) ;
m3 = m1 [ 4 ] . match ( /[a-z\-]*/ ) ;
if ( m2 && m3 ) {
switch ( m3 [ 0 ] ) {
case 'farthest-corner' :
case 'cover' : // is equivalent to farthest-corner
case '' : // mozilla removes "cover" from definition :(
tl = Math . sqrt ( Math . pow ( gradient . cx , 2 ) + Math . pow ( gradient . cy , 2 ) ) ;
tr = Math . sqrt ( Math . pow ( gradient . cx , 2 ) + Math . pow ( gradient . y1 - gradient . cy , 2 ) ) ;
br = Math . sqrt ( Math . pow ( gradient . x1 - gradient . cx , 2 ) + Math . pow ( gradient . y1 - gradient . cy , 2 ) ) ;
bl = Math . sqrt ( Math . pow ( gradient . x1 - gradient . cx , 2 ) + Math . pow ( gradient . cy , 2 ) ) ;
gradient . rx = gradient . ry = Math . max ( tl , tr , br , bl ) ;
break ;
case 'closest-corner' :
tl = Math . sqrt ( Math . pow ( gradient . cx , 2 ) + Math . pow ( gradient . cy , 2 ) ) ;
tr = Math . sqrt ( Math . pow ( gradient . cx , 2 ) + Math . pow ( gradient . y1 - gradient . cy , 2 ) ) ;
br = Math . sqrt ( Math . pow ( gradient . x1 - gradient . cx , 2 ) + Math . pow ( gradient . y1 - gradient . cy , 2 ) ) ;
bl = Math . sqrt ( Math . pow ( gradient . x1 - gradient . cx , 2 ) + Math . pow ( gradient . cy , 2 ) ) ;
gradient . rx = gradient . ry = Math . min ( tl , tr , br , bl ) ;
break ;
case 'farthest-side' :
if ( m2 [ 0 ] === 'circle' ) {
gradient . rx = gradient . ry = Math . max (
gradient . cx ,
gradient . cy ,
gradient . x1 - gradient . cx ,
gradient . y1 - gradient . cy
) ;
} else { // ellipse
gradient . type = m2 [ 0 ] ;
gradient . rx = Math . max (
gradient . cx ,
gradient . x1 - gradient . cx
) ;
gradient . ry = Math . max (
gradient . cy ,
gradient . y1 - gradient . cy
) ;
}
break ;
case 'closest-side' :
case 'contain' : // is equivalent to closest-side
if ( m2 [ 0 ] === 'circle' ) {
gradient . rx = gradient . ry = Math . min (
gradient . cx ,
gradient . cy ,
gradient . x1 - gradient . cx ,
gradient . y1 - gradient . cy
) ;
} else { // ellipse
gradient . type = m2 [ 0 ] ;
gradient . rx = Math . min (
gradient . cx ,
gradient . x1 - gradient . cx
) ;
gradient . ry = Math . min (
gradient . cy ,
gradient . y1 - gradient . cy
) ;
}
break ;
// TODO: add support for "30px 40px" sizes (webkit only)
}
}
// color stops
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 ) ;
if ( m2 ) {
m2Len = m2 . length ;
step = 1 / Math . max ( m2Len - 1 , 1 ) ;
for ( i = 0 ; i < m2Len ; i += 1 ) {
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)?/ ) ;
if ( m3 [ 2 ] ) {
stop = parseFloat ( m3 [ 2 ] ) ;
if ( m3 [ 3 ] === '%' ) {
stop /= 100 ;
} else { // px - stupid opera
stop /= bounds . width ;
}
} else {
stop = i * step ;
}
gradient . colorStops . push ( {
color : m3 [ 1 ] ,
stop : stop
} ) ;
}
}
break ;
}
}
return gradient ;
} ;
_html2canvas . Generate . Gradient = function ( src , bounds ) {
if ( bounds . width === 0 || bounds . height === 0 ) {
return ;
}
var canvas = document . createElement ( 'canvas' ) ,
ctx = canvas . getContext ( '2d' ) ,
gradient , grad , i , len ;
canvas . width = bounds . width ;
canvas . height = bounds . height ;
// TODO: add support for multi defined background gradients
gradient = _html2canvas . Generate . parseGradient ( src , bounds ) ;
if ( gradient ) {
if ( gradient . type === 'linear' ) {
grad = ctx . createLinearGradient ( gradient . x0 , gradient . y0 , gradient . x1 , gradient . y1 ) ;
for ( i = 0 , len = gradient . colorStops . length ; i < len ; i += 1 ) {
try {
grad . addColorStop ( gradient . colorStops [ i ] . stop , gradient . colorStops [ i ] . color ) ;
}
catch ( e ) {
h2clog ( [ 'failed to add color stop: ' , e , '; tried to add: ' , gradient . colorStops [ i ] , '; stop: ' , i , '; in: ' , src ] ) ;
}
}
ctx . fillStyle = grad ;
ctx . fillRect ( 0 , 0 , bounds . width , bounds . height ) ;
} else if ( gradient . type === 'circle' ) {
grad = ctx . createRadialGradient ( gradient . cx , gradient . cy , 0 , gradient . cx , gradient . cy , gradient . rx ) ;
for ( i = 0 , len = gradient . colorStops . length ; i < len ; i += 1 ) {
try {
grad . addColorStop ( gradient . colorStops [ i ] . stop , gradient . colorStops [ i ] . color ) ;
}
catch ( e ) {
h2clog ( [ 'failed to add color stop: ' , e , '; tried to add: ' , gradient . colorStops [ i ] , '; stop: ' , i , '; in: ' , src ] ) ;
}
}
ctx . fillStyle = grad ;
ctx . fillRect ( 0 , 0 , bounds . width , bounds . height ) ;
} else if ( gradient . type === 'ellipse' ) {
// draw circle
var canvasRadial = document . createElement ( 'canvas' ) ,
ctxRadial = canvasRadial . getContext ( '2d' ) ,
ri = Math . max ( gradient . rx , gradient . ry ) ,
di = ri * 2 , imgRadial ;
canvasRadial . width = canvasRadial . height = di ;
grad = ctxRadial . createRadialGradient ( gradient . rx , gradient . ry , 0 , gradient . rx , gradient . ry , ri ) ;
for ( i = 0 , len = gradient . colorStops . length ; i < len ; i += 1 ) {
try {
grad . addColorStop ( gradient . colorStops [ i ] . stop , gradient . colorStops [ i ] . color ) ;
}
catch ( e ) {
h2clog ( [ 'failed to add color stop: ' , e , '; tried to add: ' , gradient . colorStops [ i ] , '; stop: ' , i , '; in: ' , src ] ) ;
}
}
ctxRadial . fillStyle = grad ;
ctxRadial . fillRect ( 0 , 0 , di , di ) ;
ctx . fillStyle = gradient . colorStops [ i - 1 ] . color ;
ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
ctx . drawImage ( canvasRadial , gradient . cx - gradient . rx , gradient . cy - gradient . ry , 2 * gradient . rx , 2 * gradient . ry ) ;
}
}
return canvas ;
} ;
_html2canvas . Generate . ListAlpha = function ( number ) {
var tmp = "" ,
modulus ;
do {
modulus = number % 26 ;
tmp = String . fromCharCode ( ( modulus ) + 64 ) + tmp ;
number = number / 26 ;
} while ( ( number * 26 ) > 26 ) ;
return tmp ;
} ;
_html2canvas . Generate . ListRoman = function ( number ) {
var romanArray = [ "M" , "CM" , "D" , "CD" , "C" , "XC" , "L" , "XL" , "X" , "IX" , "V" , "IV" , "I" ] ,
decimal = [ 1000 , 900 , 500 , 400 , 100 , 90 , 50 , 40 , 10 , 9 , 5 , 4 , 1 ] ,
roman = "" ,
v ,
len = romanArray . length ;
if ( number <= 0 || number >= 4000 ) {
return number ;
}
for ( v = 0 ; v < len ; v += 1 ) {
while ( number >= decimal [ v ] ) {
number -= decimal [ v ] ;
roman += romanArray [ v ] ;
}
}
return roman ;
} ;
} ) ( ) ;
2013-04-12 15:50:41 +07:00
function h2cRenderContext ( width , height ) {
var storage = [ ] ;
return {
storage : storage ,
width : width ,
height : height ,
clip : function ( ) {
storage . push ( {
type : "function" ,
name : "clip" ,
'arguments' : arguments
} ) ;
} ,
translate : function ( ) {
storage . push ( {
type : "function" ,
name : "translate" ,
'arguments' : arguments
} ) ;
} ,
fill : function ( ) {
storage . push ( {
type : "function" ,
name : "fill" ,
'arguments' : arguments
} ) ;
} ,
save : function ( ) {
storage . push ( {
type : "function" ,
name : "save" ,
'arguments' : arguments
} ) ;
} ,
restore : function ( ) {
storage . push ( {
type : "function" ,
name : "restore" ,
'arguments' : arguments
} ) ;
} ,
fillRect : function ( ) {
storage . push ( {
type : "function" ,
name : "fillRect" ,
'arguments' : arguments
} ) ;
} ,
createPattern : function ( ) {
storage . push ( {
type : "function" ,
name : "createPattern" ,
'arguments' : arguments
} ) ;
} ,
drawShape : function ( ) {
var shape = [ ] ;
storage . push ( {
type : "function" ,
name : "drawShape" ,
'arguments' : shape
} ) ;
return {
moveTo : function ( ) {
shape . push ( {
name : "moveTo" ,
'arguments' : arguments
} ) ;
} ,
lineTo : function ( ) {
shape . push ( {
name : "lineTo" ,
'arguments' : arguments
} ) ;
} ,
arcTo : function ( ) {
shape . push ( {
name : "arcTo" ,
'arguments' : arguments
} ) ;
} ,
bezierCurveTo : function ( ) {
shape . push ( {
name : "bezierCurveTo" ,
'arguments' : arguments
} ) ;
} ,
quadraticCurveTo : function ( ) {
shape . push ( {
name : "quadraticCurveTo" ,
'arguments' : arguments
} ) ;
}
} ;
} ,
drawImage : function ( ) {
storage . push ( {
type : "function" ,
name : "drawImage" ,
'arguments' : arguments
} ) ;
} ,
fillText : function ( ) {
storage . push ( {
type : "function" ,
name : "fillText" ,
'arguments' : arguments
} ) ;
} ,
setVariable : function ( variable , value ) {
storage . push ( {
type : "variable" ,
name : variable ,
'arguments' : value
} ) ;
}
} ;
}
2013-02-28 15:00:04 +07:00
_html2canvas . Parse = function ( images , options ) {
window . scroll ( 0 , 0 ) ;
var element = ( ( options . elements === undefined ) ? document . body : options . elements [ 0 ] ) , // select body by default
numDraws = 0 ,
doc = element . ownerDocument ,
support = _html2canvas . Util . Support ( options , doc ) ,
ignoreElementsRegExp = new RegExp ( "(" + options . ignoreElements + ")" ) ,
body = doc . body ,
getCSS = _html2canvas . Util . getCSS ,
pseudoHide = "___html2canvas___pseudoelement" ,
hidePseudoElements = doc . createElement ( 'style' ) ;
hidePseudoElements . innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }' ;
body . appendChild ( hidePseudoElements ) ;
images = images || { } ;
function documentWidth ( ) {
return Math . max (
Math . max ( doc . body . scrollWidth , doc . documentElement . scrollWidth ) ,
Math . max ( doc . body . offsetWidth , doc . documentElement . offsetWidth ) ,
Math . max ( doc . body . clientWidth , doc . documentElement . clientWidth )
) ;
}
function documentHeight ( ) {
return Math . max (
Math . max ( doc . body . scrollHeight , doc . documentElement . scrollHeight ) ,
Math . max ( doc . body . offsetHeight , doc . documentElement . offsetHeight ) ,
Math . max ( doc . body . clientHeight , doc . documentElement . clientHeight )
) ;
}
function getCSSInt ( element , attribute ) {
var val = parseInt ( getCSS ( element , attribute ) , 10 ) ;
return ( isNaN ( val ) ) ? 0 : val ; // borders in old IE are throwing 'medium' for demo.html
}
function renderRect ( ctx , x , y , w , h , bgcolor ) {
if ( bgcolor !== "transparent" ) {
ctx . setVariable ( "fillStyle" , bgcolor ) ;
ctx . fillRect ( x , y , w , h ) ;
numDraws += 1 ;
}
}
function textTransform ( text , transform ) {
switch ( transform ) {
case "lowercase" :
return text . toLowerCase ( ) ;
case "capitalize" :
return text . replace ( /(^|\s|:|-|\(|\))([a-z])/g , function ( m , p1 , p2 ) {
if ( m . length > 0 ) {
return p1 + p2 . toUpperCase ( ) ;
}
} ) ;
case "uppercase" :
return text . toUpperCase ( ) ;
default :
return text ;
}
}
function noLetterSpacing ( letter _spacing ) {
return ( /^(normal|none|0px)$/ . test ( letter _spacing ) ) ;
}
function drawText ( currentText , x , y , ctx ) {
if ( currentText !== null && _html2canvas . Util . trimText ( currentText ) . length > 0 ) {
ctx . fillText ( currentText , x , y ) ;
numDraws += 1 ;
}
}
function setTextVariables ( ctx , el , text _decoration , color ) {
var align = false ,
bold = getCSS ( el , "fontWeight" ) ,
family = getCSS ( el , "fontFamily" ) ,
size = getCSS ( el , "fontSize" ) ;
switch ( parseInt ( bold , 10 ) ) {
case 401 :
bold = "bold" ;
break ;
case 400 :
bold = "normal" ;
break ;
}
ctx . setVariable ( "fillStyle" , color ) ;
ctx . setVariable ( "font" , [ getCSS ( el , "fontStyle" ) , getCSS ( el , "fontVariant" ) , bold , size , family ] . join ( " " ) ) ;
ctx . setVariable ( "textAlign" , ( align ) ? "right" : "left" ) ;
if ( text _decoration !== "none" ) {
return _html2canvas . Util . Font ( family , size , doc ) ;
}
}
function renderTextDecoration ( ctx , text _decoration , bounds , metrics , color ) {
switch ( text _decoration ) {
case "underline" :
// Draws a line at the baseline of the font
// 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
renderRect ( ctx , bounds . left , Math . round ( bounds . top + metrics . baseline + metrics . lineWidth ) , bounds . width , 1 , color ) ;
break ;
case "overline" :
renderRect ( ctx , bounds . left , Math . round ( bounds . top ) , bounds . width , 1 , color ) ;
break ;
case "line-through" :
// TODO try and find exact position for line-through
renderRect ( ctx , bounds . left , Math . ceil ( bounds . top + metrics . middle + metrics . lineWidth ) , bounds . width , 1 , color ) ;
break ;
}
}
function getTextBounds ( state , text , textDecoration , isLast ) {
var bounds ;
if ( support . rangeBounds ) {
if ( textDecoration !== "none" || _html2canvas . Util . trimText ( text ) . length !== 0 ) {
bounds = textRangeBounds ( text , state . node , state . textOffset ) ;
}
state . textOffset += text . length ;
} else if ( state . node && typeof state . node . nodeValue === "string" ) {
var newTextNode = ( isLast ) ? state . node . splitText ( text . length ) : null ;
bounds = textWrapperBounds ( state . node ) ;
state . node = newTextNode ;
}
return bounds ;
}
function textRangeBounds ( text , textNode , textOffset ) {
var range = doc . createRange ( ) ;
range . setStart ( textNode , textOffset ) ;
range . setEnd ( textNode , textOffset + text . length ) ;
return range . getBoundingClientRect ( ) ;
}
function textWrapperBounds ( oldTextNode ) {
var parent = oldTextNode . parentNode ,
wrapElement = doc . createElement ( 'wrapper' ) ,
backupText = oldTextNode . cloneNode ( true ) ;
wrapElement . appendChild ( oldTextNode . cloneNode ( true ) ) ;
parent . replaceChild ( wrapElement , oldTextNode ) ;
var bounds = _html2canvas . Util . Bounds ( wrapElement ) ;
parent . replaceChild ( backupText , wrapElement ) ;
return bounds ;
}
function renderText ( el , textNode , stack ) {
var ctx = stack . ctx ,
color = getCSS ( el , "color" ) ,
textDecoration = getCSS ( el , "textDecoration" ) ,
textAlign = getCSS ( el , "textAlign" ) ,
metrics ,
textList ,
state = {
node : textNode ,
textOffset : 0
} ;
if ( _html2canvas . Util . trimText ( textNode . nodeValue ) . length > 0 ) {
textNode . nodeValue = textTransform ( textNode . nodeValue , getCSS ( el , "textTransform" ) ) ;
textAlign = textAlign . replace ( [ "-webkit-auto" ] , [ "auto" ] ) ;
textList = ( ! options . letterRendering && /^(left|right|justify|auto)$/ . test ( textAlign ) && noLetterSpacing ( getCSS ( el , "letterSpacing" ) ) ) ?
textNode . nodeValue . split ( /(\b| )/ )
: textNode . nodeValue . split ( "" ) ;
metrics = setTextVariables ( ctx , el , textDecoration , color ) ;
if ( options . chinese ) {
textList . forEach ( function ( word , index ) {
if ( /.*[\u4E00-\u9FA5].*$/ . test ( word ) ) {
word = word . split ( "" ) ;
word . unshift ( index , 1 ) ;
textList . splice . apply ( textList , word ) ;
}
} ) ;
}
textList . forEach ( function ( text , index ) {
var bounds = getTextBounds ( state , text , textDecoration , ( index < textList . length - 1 ) ) ;
if ( bounds ) {
drawText ( text , bounds . left , bounds . bottom , ctx ) ;
renderTextDecoration ( ctx , textDecoration , bounds , metrics , color ) ;
}
} ) ;
}
}
function listPosition ( element , val ) {
var boundElement = doc . createElement ( "boundelement" ) ,
originalType ,
bounds ;
boundElement . style . display = "inline" ;
originalType = element . style . listStyleType ;
element . style . listStyleType = "none" ;
boundElement . appendChild ( doc . createTextNode ( val ) ) ;
element . insertBefore ( boundElement , element . firstChild ) ;
bounds = _html2canvas . Util . Bounds ( boundElement ) ;
element . removeChild ( boundElement ) ;
element . style . listStyleType = originalType ;
return bounds ;
}
function elementIndex ( el ) {
var i = - 1 ,
count = 1 ,
childs = el . parentNode . childNodes ;
if ( el . parentNode ) {
while ( childs [ ++ i ] !== el ) {
if ( childs [ i ] . nodeType === 1 ) {
count ++ ;
}
}
return count ;
} else {
return - 1 ;
}
}
function listItemText ( element , type ) {
var currentIndex = elementIndex ( element ) ,
text ;
switch ( type ) {
case "decimal" :
text = currentIndex ;
break ;
case "decimal-leading-zero" :
text = ( currentIndex . toString ( ) . length === 1 ) ? currentIndex = "0" + currentIndex . toString ( ) : currentIndex . toString ( ) ;
break ;
case "upper-roman" :
text = _html2canvas . Generate . ListRoman ( currentIndex ) ;
break ;
case "lower-roman" :
text = _html2canvas . Generate . ListRoman ( currentIndex ) . toLowerCase ( ) ;
break ;
case "lower-alpha" :
text = _html2canvas . Generate . ListAlpha ( currentIndex ) . toLowerCase ( ) ;
break ;
case "upper-alpha" :
text = _html2canvas . Generate . ListAlpha ( currentIndex ) ;
break ;
}
text += ". " ;
return text ;
}
function renderListItem ( element , stack , elBounds ) {
var x ,
text ,
ctx = stack . ctx ,
type = getCSS ( element , "listStyleType" ) ,
listBounds ;
if ( /^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i . test ( type ) ) {
text = listItemText ( element , type ) ;
listBounds = listPosition ( element , text ) ;
setTextVariables ( ctx , element , "none" , getCSS ( element , "color" ) ) ;
if ( getCSS ( element , "listStylePosition" ) === "inside" ) {
ctx . setVariable ( "textAlign" , "left" ) ;
x = elBounds . left ;
} else {
return ;
}
drawText ( text , x , listBounds . bottom , ctx ) ;
}
}
function loadImage ( src ) {
var img = images [ src ] ;
if ( img && img . succeeded === true ) {
return img . img ;
} else {
return false ;
}
}
function clipBounds ( src , dst ) {
var x = Math . max ( src . left , dst . left ) ,
y = Math . max ( src . top , dst . top ) ,
x2 = Math . min ( ( src . left + src . width ) , ( dst . left + dst . width ) ) ,
y2 = Math . min ( ( src . top + src . height ) , ( dst . top + dst . height ) ) ;
return {
left : x ,
top : y ,
width : x2 - x ,
height : y2 - y
} ;
}
function setZ ( zIndex , parentZ ) {
// TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them
var newContext ;
if ( ! parentZ ) {
newContext = h2czContext ( 0 ) ;
return newContext ;
}
if ( zIndex !== "auto" ) {
newContext = h2czContext ( zIndex ) ;
parentZ . children . push ( newContext ) ;
return newContext ;
}
return parentZ ;
}
function renderImage ( ctx , element , image , bounds , borders ) {
var paddingLeft = getCSSInt ( element , 'paddingLeft' ) ,
paddingTop = getCSSInt ( element , 'paddingTop' ) ,
paddingRight = getCSSInt ( element , 'paddingRight' ) ,
paddingBottom = getCSSInt ( element , 'paddingBottom' ) ;
drawImage (
ctx ,
image ,
0 , //sx
0 , //sy
image . width , //sw
image . height , //sh
bounds . left + paddingLeft + borders [ 3 ] . width , //dx
bounds . top + paddingTop + borders [ 0 ] . width , // dy
bounds . width - ( borders [ 1 ] . width + borders [ 3 ] . width + paddingLeft + paddingRight ) , //dw
bounds . height - ( borders [ 0 ] . width + borders [ 2 ] . width + paddingTop + paddingBottom ) //dh
) ;
}
function getBorderData ( element ) {
return [ "Top" , "Right" , "Bottom" , "Left" ] . map ( function ( side ) {
return {
width : getCSSInt ( element , 'border' + side + 'Width' ) ,
color : getCSS ( element , 'border' + side + 'Color' )
} ;
} ) ;
}
function getBorderRadiusData ( element ) {
return [ "TopLeft" , "TopRight" , "BottomRight" , "BottomLeft" ] . map ( function ( side ) {
return getCSS ( element , 'border' + side + 'Radius' ) ;
} ) ;
}
var getCurvePoints = ( function ( kappa ) {
return function ( x , y , r1 , r2 ) {
var ox = ( r1 ) * kappa , // control point offset horizontal
oy = ( r2 ) * kappa , // control point offset vertical
xm = x + r1 , // x-middle
ym = y + r2 ; // y-middle
return {
topLeft : bezierCurve ( {
x : x ,
y : ym
} , {
x : x ,
y : ym - oy
} , {
x : xm - ox ,
y : y
} , {
x : xm ,
y : y
} ) ,
topRight : bezierCurve ( {
x : x ,
y : y
} , {
x : x + ox ,
y : y
} , {
x : xm ,
y : ym - oy
} , {
x : xm ,
y : ym
} ) ,
bottomRight : bezierCurve ( {
x : xm ,
y : y
} , {
x : xm ,
y : y + oy
} , {
x : x + ox ,
y : ym
} , {
x : x ,
y : ym
} ) ,
bottomLeft : bezierCurve ( {
x : xm ,
y : ym
} , {
x : xm - ox ,
y : ym
} , {
x : x ,
y : y + oy
} , {
x : x ,
y : y
} )
} ;
} ;
} ) ( 4 * ( ( Math . sqrt ( 2 ) - 1 ) / 3 ) ) ;
function bezierCurve ( start , startControl , endControl , end ) {
var lerp = function ( a , b , t ) {
return {
x : a . x + ( b . x - a . x ) * t ,
y : a . y + ( b . y - a . y ) * t
} ;
} ;
return {
start : start ,
startControl : startControl ,
endControl : endControl ,
end : end ,
subdivide : function ( t ) {
var ab = lerp ( start , startControl , t ) ,
bc = lerp ( startControl , endControl , t ) ,
cd = lerp ( endControl , end , t ) ,
abbc = lerp ( ab , bc , t ) ,
bccd = lerp ( bc , cd , t ) ,
dest = lerp ( abbc , bccd , t ) ;
return [ bezierCurve ( start , ab , abbc , dest ) , bezierCurve ( dest , bccd , cd , end ) ] ;
} ,
curveTo : function ( borderArgs ) {
borderArgs . push ( [ "bezierCurve" , startControl . x , startControl . y , endControl . x , endControl . y , end . x , end . y ] ) ;
} ,
curveToReversed : function ( borderArgs ) {
borderArgs . push ( [ "bezierCurve" , endControl . x , endControl . y , startControl . x , startControl . y , start . x , start . y ] ) ;
}
} ;
}
function parseCorner ( borderArgs , radius1 , radius2 , corner1 , corner2 , x , y ) {
if ( radius1 [ 0 ] > 0 || radius1 [ 1 ] > 0 ) {
borderArgs . push ( [ "line" , corner1 [ 0 ] . start . x , corner1 [ 0 ] . start . y ] ) ;
corner1 [ 0 ] . curveTo ( borderArgs ) ;
corner1 [ 1 ] . curveTo ( borderArgs ) ;
} else {
borderArgs . push ( [ "line" , x , y ] ) ;
}
if ( radius2 [ 0 ] > 0 || radius2 [ 1 ] > 0 ) {
borderArgs . push ( [ "line" , corner2 [ 0 ] . start . x , corner2 [ 0 ] . start . y ] ) ;
}
}
function drawSide ( borderData , radius1 , radius2 , outer1 , inner1 , outer2 , inner2 ) {
var borderArgs = [ ] ;
if ( radius1 [ 0 ] > 0 || radius1 [ 1 ] > 0 ) {
borderArgs . push ( [ "line" , outer1 [ 1 ] . start . x , outer1 [ 1 ] . start . y ] ) ;
outer1 [ 1 ] . curveTo ( borderArgs ) ;
} else {
borderArgs . push ( [ "line" , borderData . c1 [ 0 ] , borderData . c1 [ 1 ] ] ) ;
}
if ( radius2 [ 0 ] > 0 || radius2 [ 1 ] > 0 ) {
borderArgs . push ( [ "line" , outer2 [ 0 ] . start . x , outer2 [ 0 ] . start . y ] ) ;
outer2 [ 0 ] . curveTo ( borderArgs ) ;
borderArgs . push ( [ "line" , inner2 [ 0 ] . end . x , inner2 [ 0 ] . end . y ] ) ;
inner2 [ 0 ] . curveToReversed ( borderArgs ) ;
} else {
borderArgs . push ( [ "line" , borderData . c2 [ 0 ] , borderData . c2 [ 1 ] ] ) ;
borderArgs . push ( [ "line" , borderData . c3 [ 0 ] , borderData . c3 [ 1 ] ] ) ;
}
if ( radius1 [ 0 ] > 0 || radius1 [ 1 ] > 0 ) {
borderArgs . push ( [ "line" , inner1 [ 1 ] . end . x , inner1 [ 1 ] . end . y ] ) ;
inner1 [ 1 ] . curveToReversed ( borderArgs ) ;
} else {
borderArgs . push ( [ "line" , borderData . c4 [ 0 ] , borderData . c4 [ 1 ] ] ) ;
}
return borderArgs ;
}
function calculateCurvePoints ( bounds , borderRadius , borders ) {
var x = bounds . left ,
y = bounds . top ,
width = bounds . width ,
height = bounds . height ,
tlh = borderRadius [ 0 ] [ 0 ] ,
tlv = borderRadius [ 0 ] [ 1 ] ,
trh = borderRadius [ 1 ] [ 0 ] ,
trv = borderRadius [ 1 ] [ 1 ] ,
brv = borderRadius [ 2 ] [ 0 ] ,
brh = borderRadius [ 2 ] [ 1 ] ,
blh = borderRadius [ 3 ] [ 0 ] ,
blv = borderRadius [ 3 ] [ 1 ] ,
topWidth = width - trh ,
rightHeight = height - brv ,
bottomWidth = width - brh ,
leftHeight = height - blv ;
return {
topLeftOuter : getCurvePoints (
x ,
y ,
tlh ,
tlv
) . topLeft . subdivide ( 0.5 ) ,
topLeftInner : getCurvePoints (
x + borders [ 3 ] . width ,
y + borders [ 0 ] . width ,
Math . max ( 0 , tlh - borders [ 3 ] . width ) ,
Math . max ( 0 , tlv - borders [ 0 ] . width )
) . topLeft . subdivide ( 0.5 ) ,
topRightOuter : getCurvePoints (
x + topWidth ,
y ,
trh ,
trv
) . topRight . subdivide ( 0.5 ) ,
topRightInner : getCurvePoints (
x + Math . min ( topWidth , width + borders [ 3 ] . width ) ,
y + borders [ 0 ] . width ,
( topWidth > width + borders [ 3 ] . width ) ? 0 : trh - borders [ 3 ] . width ,
trv - borders [ 0 ] . width
) . topRight . subdivide ( 0.5 ) ,
bottomRightOuter : getCurvePoints (
x + bottomWidth ,
y + rightHeight ,
brh ,
brv
) . bottomRight . subdivide ( 0.5 ) ,
bottomRightInner : getCurvePoints (
x + Math . min ( bottomWidth , width + borders [ 3 ] . width ) ,
y + Math . min ( rightHeight , height + borders [ 0 ] . width ) ,
Math . max ( 0 , brh - borders [ 1 ] . width ) ,
Math . max ( 0 , brv - borders [ 2 ] . width )
) . bottomRight . subdivide ( 0.5 ) ,
bottomLeftOuter : getCurvePoints (
x ,
y + leftHeight ,
blh ,
blv
) . bottomLeft . subdivide ( 0.5 ) ,
bottomLeftInner : getCurvePoints (
x + borders [ 3 ] . width ,
y + leftHeight ,
Math . max ( 0 , blh - borders [ 3 ] . width ) ,
Math . max ( 0 , blv - borders [ 2 ] . width )
) . bottomLeft . subdivide ( 0.5 )
} ;
}
function getBorderClip ( element , borderPoints , borders , radius , bounds ) {
var backgroundClip = getCSS ( element , 'backgroundClip' ) ,
borderArgs = [ ] ;
switch ( backgroundClip ) {
case "content-box" :
case "padding-box" :
parseCorner ( borderArgs , radius [ 0 ] , radius [ 1 ] , borderPoints . topLeftInner , borderPoints . topRightInner , bounds . left + borders [ 3 ] . width , bounds . top + borders [ 0 ] . width ) ;
parseCorner ( borderArgs , radius [ 1 ] , radius [ 2 ] , borderPoints . topRightInner , borderPoints . bottomRightInner , bounds . left + bounds . width - borders [ 1 ] . width , bounds . top + borders [ 0 ] . width ) ;
parseCorner ( borderArgs , radius [ 2 ] , radius [ 3 ] , borderPoints . bottomRightInner , borderPoints . bottomLeftInner , bounds . left + bounds . width - borders [ 1 ] . width , bounds . top + bounds . height - borders [ 2 ] . width ) ;
parseCorner ( borderArgs , radius [ 3 ] , radius [ 0 ] , borderPoints . bottomLeftInner , borderPoints . topLeftInner , bounds . left + borders [ 3 ] . width , bounds . top + bounds . height - borders [ 2 ] . width ) ;
break ;
default :
parseCorner ( borderArgs , radius [ 0 ] , radius [ 1 ] , borderPoints . topLeftOuter , borderPoints . topRightOuter , bounds . left , bounds . top ) ;
parseCorner ( borderArgs , radius [ 1 ] , radius [ 2 ] , borderPoints . topRightOuter , borderPoints . bottomRightOuter , bounds . left + bounds . width , bounds . top ) ;
parseCorner ( borderArgs , radius [ 2 ] , radius [ 3 ] , borderPoints . bottomRightOuter , borderPoints . bottomLeftOuter , bounds . left + bounds . width , bounds . top + bounds . height ) ;
parseCorner ( borderArgs , radius [ 3 ] , radius [ 0 ] , borderPoints . bottomLeftOuter , borderPoints . topLeftOuter , bounds . left , bounds . top + bounds . height ) ;
break ;
}
return borderArgs ;
}
function parseBorders ( element , bounds , borders ) {
var x = bounds . left ,
y = bounds . top ,
width = bounds . width ,
height = bounds . height ,
borderSide ,
bx ,
by ,
bw ,
bh ,
borderArgs ,
// http://www.w3.org/TR/css3-background/#the-border-radius
borderRadius = getBorderRadiusData ( element ) ,
borderPoints = calculateCurvePoints ( bounds , borderRadius , borders ) ,
borderData = {
clip : getBorderClip ( element , borderPoints , borders , borderRadius , bounds ) ,
borders : [ ]
} ;
for ( borderSide = 0 ; borderSide < 4 ; borderSide ++ ) {
if ( borders [ borderSide ] . width > 0 ) {
bx = x ;
by = y ;
bw = width ;
bh = height - ( borders [ 2 ] . width ) ;
switch ( borderSide ) {
case 0 :
// top border
bh = borders [ 0 ] . width ;
borderArgs = drawSide ( {
c1 : [ bx , by ] ,
c2 : [ bx + bw , by ] ,
c3 : [ bx + bw - borders [ 1 ] . width , by + bh ] ,
c4 : [ bx + borders [ 3 ] . width , by + bh ]
} , borderRadius [ 0 ] , borderRadius [ 1 ] ,
borderPoints . topLeftOuter , borderPoints . topLeftInner , borderPoints . topRightOuter , borderPoints . topRightInner ) ;
break ;
case 1 :
// right border
bx = x + width - ( borders [ 1 ] . width ) ;
bw = borders [ 1 ] . width ;
borderArgs = drawSide ( {
c1 : [ bx + bw , by ] ,
c2 : [ bx + bw , by + bh + borders [ 2 ] . width ] ,
c3 : [ bx , by + bh ] ,
c4 : [ bx , by + borders [ 0 ] . width ]
} , borderRadius [ 1 ] , borderRadius [ 2 ] ,
borderPoints . topRightOuter , borderPoints . topRightInner , borderPoints . bottomRightOuter , borderPoints . bottomRightInner ) ;
break ;
case 2 :
// bottom border
by = ( by + height ) - ( borders [ 2 ] . width ) ;
bh = borders [ 2 ] . width ;
borderArgs = drawSide ( {
c1 : [ bx + bw , by + bh ] ,
c2 : [ bx , by + bh ] ,
c3 : [ bx + borders [ 3 ] . width , by ] ,
c4 : [ bx + bw - borders [ 2 ] . width , by ]
} , borderRadius [ 2 ] , borderRadius [ 3 ] ,
borderPoints . bottomRightOuter , borderPoints . bottomRightInner , borderPoints . bottomLeftOuter , borderPoints . bottomLeftInner ) ;
break ;
case 3 :
// left border
bw = borders [ 3 ] . width ;
borderArgs = drawSide ( {
c1 : [ bx , by + bh + borders [ 2 ] . width ] ,
c2 : [ bx , by ] ,
c3 : [ bx + bw , by + borders [ 0 ] . width ] ,
c4 : [ bx + bw , by + bh ]
} , borderRadius [ 3 ] , borderRadius [ 0 ] ,
borderPoints . bottomLeftOuter , borderPoints . bottomLeftInner , borderPoints . topLeftOuter , borderPoints . topLeftInner ) ;
break ;
}
borderData . borders . push ( {
args : borderArgs ,
color : borders [ borderSide ] . color
} ) ;
}
}
return borderData ;
}
function createShape ( ctx , args ) {
var shape = ctx . drawShape ( ) ;
args . forEach ( function ( border , index ) {
shape [ ( index === 0 ) ? "moveTo" : border [ 0 ] + "To" ] . apply ( null , border . slice ( 1 ) ) ;
} ) ;
return shape ;
}
function renderBorders ( ctx , borderArgs , color ) {
if ( color !== "transparent" ) {
ctx . setVariable ( "fillStyle" , color ) ;
createShape ( ctx , borderArgs ) ;
ctx . fill ( ) ;
numDraws += 1 ;
}
}
function renderFormValue ( el , bounds , stack ) {
var valueWrap = doc . createElement ( 'valuewrap' ) ,
cssPropertyArray = [ 'lineHeight' , 'textAlign' , 'fontFamily' , 'color' , 'fontSize' , 'paddingLeft' , 'paddingTop' , 'width' , 'height' , 'border' , 'borderLeftWidth' , 'borderTopWidth' ] ,
textValue ,
textNode ;
cssPropertyArray . forEach ( function ( property ) {
try {
valueWrap . style [ property ] = getCSS ( el , property ) ;
} catch ( e ) {
// Older IE has issues with "border"
h2clog ( "html2canvas: Parse: Exception caught in renderFormValue: " + e . message ) ;
}
} ) ;
valueWrap . style . borderColor = "black" ;
valueWrap . style . borderStyle = "solid" ;
valueWrap . style . display = "block" ;
valueWrap . style . position = "absolute" ;
if ( /^(submit|reset|button|text|password)$/ . test ( el . type ) || el . nodeName === "SELECT" ) {
valueWrap . style . lineHeight = getCSS ( el , "height" ) ;
}
valueWrap . style . top = bounds . top + "px" ;
valueWrap . style . left = bounds . left + "px" ;
textValue = ( el . nodeName === "SELECT" ) ? ( el . options [ el . selectedIndex ] || 0 ) . text : el . value ;
if ( ! textValue ) {
textValue = el . placeholder ;
}
textNode = doc . createTextNode ( textValue ) ;
valueWrap . appendChild ( textNode ) ;
body . appendChild ( valueWrap ) ;
renderText ( el , textNode , stack ) ;
body . removeChild ( valueWrap ) ;
}
function drawImage ( ctx ) {
ctx . drawImage . apply ( ctx , Array . prototype . slice . call ( arguments , 1 ) ) ;
numDraws += 1 ;
}
function getPseudoElement ( el , which ) {
var elStyle = document . defaultView . getComputedStyle ( el , which ) ;
2013-04-12 15:50:41 +07:00
if ( ! elStyle || ! elStyle . content || elStyle . content === "none" || elStyle . content === "-moz-alt-content" || elStyle . display === "none" ) {
2013-02-28 15:00:04 +07:00
return ;
}
var content = elStyle . content + '' ,
first = content . substr ( 0 , 1 ) ;
//strips quotes
if ( first === content . substr ( content . length - 1 ) && first . match ( /'|"/ ) ) {
content = content . substr ( 1 , content . length - 2 ) ;
}
var isImage = content . substr ( 0 , 3 ) === 'url' ,
elps = document . createElement ( isImage ? 'img' : 'span' ) ;
elps . className = pseudoHide + "-before " + pseudoHide + "-after" ;
Object . keys ( elStyle ) . filter ( indexedProperty ) . forEach ( function ( prop ) {
2013-04-12 15:50:41 +07:00
// Prevent assigning of read only CSS Rules, ex. length, parentRule
try {
elps . style [ prop ] = elStyle [ prop ] ;
} catch ( e ) {
h2clog ( [ 'Tried to assign readonly property ' , prop , 'Error:' , e ] ) ;
}
2013-02-28 15:00:04 +07:00
} ) ;
if ( isImage ) {
elps . src = _html2canvas . Util . parseBackgroundImage ( content ) [ 0 ] . args [ 0 ] ;
} else {
elps . innerHTML = content ;
}
return elps ;
}
function indexedProperty ( property ) {
return ( isNaN ( window . parseInt ( property , 10 ) ) ) ;
}
function injectPseudoElements ( el , stack ) {
var before = getPseudoElement ( el , ':before' ) ,
after = getPseudoElement ( el , ':after' ) ;
if ( ! before && ! after ) {
return ;
}
if ( before ) {
el . className += " " + pseudoHide + "-before" ;
el . parentNode . insertBefore ( before , el ) ;
parseElement ( before , stack , true ) ;
el . parentNode . removeChild ( before ) ;
el . className = el . className . replace ( pseudoHide + "-before" , "" ) . trim ( ) ;
}
if ( after ) {
el . className += " " + pseudoHide + "-after" ;
el . appendChild ( after ) ;
parseElement ( after , stack , true ) ;
el . removeChild ( after ) ;
el . className = el . className . replace ( pseudoHide + "-after" , "" ) . trim ( ) ;
}
}
function renderBackgroundRepeat ( ctx , image , backgroundPosition , bounds ) {
var offsetX = Math . round ( bounds . left + backgroundPosition . left ) ,
offsetY = Math . round ( bounds . top + backgroundPosition . top ) ;
ctx . createPattern ( image ) ;
ctx . translate ( offsetX , offsetY ) ;
ctx . fill ( ) ;
ctx . translate ( - offsetX , - offsetY ) ;
}
function backgroundRepeatShape ( ctx , image , backgroundPosition , bounds , left , top , width , height ) {
var args = [ ] ;
args . push ( [ "line" , Math . round ( left ) , Math . round ( top ) ] ) ;
args . push ( [ "line" , Math . round ( left + width ) , Math . round ( top ) ] ) ;
args . push ( [ "line" , Math . round ( left + width ) , Math . round ( height + top ) ] ) ;
args . push ( [ "line" , Math . round ( left ) , Math . round ( height + top ) ] ) ;
createShape ( ctx , args ) ;
ctx . save ( ) ;
ctx . clip ( ) ;
renderBackgroundRepeat ( ctx , image , backgroundPosition , bounds ) ;
ctx . restore ( ) ;
}
function renderBackgroundColor ( ctx , backgroundBounds , bgcolor ) {
renderRect (
ctx ,
backgroundBounds . left ,
backgroundBounds . top ,
backgroundBounds . width ,
backgroundBounds . height ,
bgcolor
) ;
}
function renderBackgroundRepeating ( el , bounds , ctx , image , imageIndex ) {
var backgroundSize = _html2canvas . Util . BackgroundSize ( el , bounds , image , imageIndex ) ,
backgroundPosition = _html2canvas . Util . BackgroundPosition ( el , bounds , image , imageIndex , backgroundSize ) ,
backgroundRepeat = getCSS ( el , "backgroundRepeat" ) . split ( "," ) . map ( function ( value ) {
return value . trim ( ) ;
} ) ;
image = resizeImage ( image , backgroundSize ) ;
backgroundRepeat = backgroundRepeat [ imageIndex ] || backgroundRepeat [ 0 ] ;
switch ( backgroundRepeat ) {
case "repeat-x" :
backgroundRepeatShape ( ctx , image , backgroundPosition , bounds ,
bounds . left , bounds . top + backgroundPosition . top , 99999 , image . height ) ;
break ;
case "repeat-y" :
backgroundRepeatShape ( ctx , image , backgroundPosition , bounds ,
bounds . left + backgroundPosition . left , bounds . top , image . width , 99999 ) ;
break ;
case "no-repeat" :
backgroundRepeatShape ( ctx , image , backgroundPosition , bounds ,
bounds . left + backgroundPosition . left , bounds . top + backgroundPosition . top , image . width , image . height ) ;
break ;
default :
renderBackgroundRepeat ( ctx , image , backgroundPosition , {
top : bounds . top ,
left : bounds . left ,
width : image . width ,
height : image . height
} ) ;
break ;
}
}
function renderBackgroundImage ( element , bounds , ctx ) {
var backgroundImage = getCSS ( element , "backgroundImage" ) ,
backgroundImages = _html2canvas . Util . parseBackgroundImage ( backgroundImage ) ,
image ,
imageIndex = backgroundImages . length ;
while ( imageIndex -- ) {
backgroundImage = backgroundImages [ imageIndex ] ;
if ( ! backgroundImage . args || backgroundImage . args . length === 0 ) {
continue ;
}
var key = backgroundImage . method === 'url' ?
backgroundImage . args [ 0 ] :
backgroundImage . value ;
image = loadImage ( key ) ;
// TODO add support for background-origin
if ( image ) {
renderBackgroundRepeating ( element , bounds , ctx , image , imageIndex ) ;
} else {
h2clog ( "html2canvas: Error loading background:" , backgroundImage ) ;
}
}
}
function resizeImage ( image , bounds ) {
if ( image . width === bounds . width && image . height === bounds . height ) {
return image ;
}
var ctx , canvas = doc . createElement ( 'canvas' ) ;
canvas . width = bounds . width ;
canvas . height = bounds . height ;
ctx = canvas . getContext ( "2d" ) ;
drawImage ( ctx , image , 0 , 0 , image . width , image . height , 0 , 0 , bounds . width , bounds . height ) ;
return canvas ;
}
function setOpacity ( ctx , element , parentStack ) {
var opacity = getCSS ( element , "opacity" ) * ( ( parentStack ) ? parentStack . opacity : 1 ) ;
ctx . setVariable ( "globalAlpha" , opacity ) ;
return opacity ;
}
function createStack ( element , parentStack , bounds ) {
var ctx = h2cRenderContext ( ( ! parentStack ) ? documentWidth ( ) : bounds . width , ( ! parentStack ) ? documentHeight ( ) : bounds . height ) ,
stack = {
ctx : ctx ,
zIndex : setZ ( getCSS ( element , "zIndex" ) , ( parentStack ) ? parentStack . zIndex : null ) ,
opacity : setOpacity ( ctx , element , parentStack ) ,
cssPosition : getCSS ( element , "position" ) ,
borders : getBorderData ( element ) ,
clip : ( parentStack && parentStack . clip ) ? _html2canvas . Util . Extend ( { } , parentStack . clip ) : null
} ;
// TODO correct overflow for absolute content residing under a static position
if ( options . useOverflow === true && /(hidden|scroll|auto)/ . test ( getCSS ( element , "overflow" ) ) === true && /(BODY)/i . test ( element . nodeName ) === false ) {
stack . clip = ( stack . clip ) ? clipBounds ( stack . clip , bounds ) : bounds ;
}
stack . zIndex . children . push ( stack ) ;
return stack ;
}
function getBackgroundBounds ( borders , bounds , clip ) {
var backgroundBounds = {
left : bounds . left + borders [ 3 ] . width ,
top : bounds . top + borders [ 0 ] . width ,
width : bounds . width - ( borders [ 1 ] . width + borders [ 3 ] . width ) ,
height : bounds . height - ( borders [ 0 ] . width + borders [ 2 ] . width )
} ;
if ( clip ) {
backgroundBounds = clipBounds ( backgroundBounds , clip ) ;
}
return backgroundBounds ;
}
function renderElement ( element , parentStack , pseudoElement ) {
var bounds = _html2canvas . Util . Bounds ( element ) ,
image ,
bgcolor = ( ignoreElementsRegExp . test ( element . nodeName ) ) ? "#efefef" : getCSS ( element , "backgroundColor" ) ,
stack = createStack ( element , parentStack , bounds ) ,
borders = stack . borders ,
ctx = stack . ctx ,
backgroundBounds = getBackgroundBounds ( borders , bounds , stack . clip ) ,
borderData = parseBorders ( element , bounds , borders ) ;
createShape ( ctx , borderData . clip ) ;
ctx . save ( ) ;
ctx . clip ( ) ;
if ( backgroundBounds . height > 0 && backgroundBounds . width > 0 ) {
renderBackgroundColor ( ctx , bounds , bgcolor ) ;
renderBackgroundImage ( element , backgroundBounds , ctx ) ;
}
ctx . restore ( ) ;
borderData . borders . forEach ( function ( border ) {
renderBorders ( ctx , border . args , border . color ) ;
} ) ;
if ( ! pseudoElement ) {
injectPseudoElements ( element , stack ) ;
}
switch ( element . nodeName ) {
case "IMG" :
if ( ( image = loadImage ( element . getAttribute ( 'src' ) ) ) ) {
renderImage ( ctx , element , image , bounds , borders ) ;
} else {
h2clog ( "html2canvas: Error loading <img>:" + element . getAttribute ( 'src' ) ) ;
}
break ;
case "INPUT" :
// TODO add all relevant type's, i.e. HTML5 new stuff
// todo add support for placeholder attribute for browsers which support it
2013-04-12 15:50:41 +07:00
if ( /^(text|url|email|submit|button|reset)$/ . test ( element . type ) && ( element . value || element . placeholder || "" ) . length > 0 ) {
2013-02-28 15:00:04 +07:00
renderFormValue ( element , bounds , stack ) ;
}
break ;
case "TEXTAREA" :
if ( ( element . value || element . placeholder || "" ) . length > 0 ) {
renderFormValue ( element , bounds , stack ) ;
}
break ;
case "SELECT" :
if ( ( element . options || element . placeholder || "" ) . length > 0 ) {
renderFormValue ( element , bounds , stack ) ;
}
break ;
case "LI" :
renderListItem ( element , stack , backgroundBounds ) ;
break ;
case "CANVAS" :
renderImage ( ctx , element , element , bounds , borders ) ;
break ;
}
return stack ;
}
function isElementVisible ( element ) {
return ( getCSS ( element , 'display' ) !== "none" && getCSS ( element , 'visibility' ) !== "hidden" && ! element . hasAttribute ( "data-html2canvas-ignore" ) ) ;
}
function parseElement ( el , stack , pseudoElement ) {
if ( isElementVisible ( el ) ) {
stack = renderElement ( el , stack , pseudoElement ) || stack ;
if ( ! ignoreElementsRegExp . test ( el . nodeName ) ) {
_html2canvas . Util . Children ( el ) . forEach ( function ( node ) {
if ( node . nodeType === 1 ) {
parseElement ( node , stack , pseudoElement ) ;
} else if ( node . nodeType === 3 ) {
renderText ( el , node , stack ) ;
}
} ) ;
}
}
}
function svgDOMRender ( body , stack ) {
var img = new Image ( ) ,
docWidth = documentWidth ( ) ,
docHeight = documentHeight ( ) ,
html = "" ;
function parseDOM ( el ) {
var children = _html2canvas . Util . Children ( el ) ,
len = children . length ,
attr ,
a ,
alen ,
elm ,
i ;
for ( i = 0 ; i < len ; i += 1 ) {
elm = children [ i ] ;
if ( elm . nodeType === 3 ) {
// Text node
html += elm . nodeValue . replace ( /</g , "<" ) . replace ( />/g , ">" ) ;
} else if ( elm . nodeType === 1 ) {
// Element
if ( ! /^(script|meta|title)$/ . test ( elm . nodeName . toLowerCase ( ) ) ) {
html += "<" + elm . nodeName . toLowerCase ( ) ;
// add attributes
if ( elm . hasAttributes ( ) ) {
attr = elm . attributes ;
alen = attr . length ;
for ( a = 0 ; a < alen ; a += 1 ) {
html += " " + attr [ a ] . name + '="' + attr [ a ] . value + '"' ;
}
}
html += '>' ;
parseDOM ( elm ) ;
html += "</" + elm . nodeName . toLowerCase ( ) + ">" ;
}
}
}
}
parseDOM ( body ) ;
img . src = [
"data:image/svg+xml," ,
"<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='" + docWidth + "' height='" + docHeight + "'>" ,
"<foreignObject width='" + docWidth + "' height='" + docHeight + "'>" ,
"<html xmlns='http://www.w3.org/1999/xhtml' style='margin:0;'>" ,
html . replace ( /\#/g , "%23" ) ,
"</html>" ,
"</foreignObject>" ,
"</svg>"
] . join ( "" ) ;
img . onload = function ( ) {
stack . svgRender = img ;
} ;
}
function init ( ) {
var stack = renderElement ( element , null ) ;
if ( support . svgRendering ) {
svgDOMRender ( document . documentElement , stack ) ;
}
Array . prototype . slice . call ( element . children , 0 ) . forEach ( function ( childElement ) {
parseElement ( childElement , stack ) ;
} ) ;
stack . backgroundColor = getCSS ( document . documentElement , "backgroundColor" ) ;
body . removeChild ( hidePseudoElements ) ;
return stack ;
}
return init ( ) ;
} ;
function h2czContext ( zindex ) {
return {
zindex : zindex ,
children : [ ]
} ;
}
_html2canvas . Preload = function ( options ) {
var images = {
numLoaded : 0 , // also failed are counted here
numFailed : 0 ,
numTotal : 0 ,
cleanupDone : false
} ,
pageOrigin ,
methods ,
i ,
count = 0 ,
element = options . elements [ 0 ] || document . body ,
doc = element . ownerDocument ,
2013-04-12 15:50:41 +07:00
domImages = element . getElementsByTagName ( 'img' ) , // Fetch images of the present element only
2013-02-28 15:00:04 +07:00
imgLen = domImages . length ,
link = doc . createElement ( "a" ) ,
supportCORS = ( function ( img ) {
return ( img . crossOrigin !== undefined ) ;
} ) ( new Image ( ) ) ,
timeoutTimer ;
link . href = window . location . href ;
pageOrigin = link . protocol + link . host ;
function isSameOrigin ( url ) {
link . href = url ;
link . href = link . href ; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
var origin = link . protocol + link . host ;
return ( origin === pageOrigin ) ;
}
function start ( ) {
h2clog ( "html2canvas: start: images: " + images . numLoaded + " / " + images . numTotal + " (failed: " + images . numFailed + ")" ) ;
if ( ! images . firstRun && images . numLoaded >= images . numTotal ) {
h2clog ( "Finished loading images: # " + images . numTotal + " (failed: " + images . numFailed + ")" ) ;
if ( typeof options . complete === "function" ) {
options . complete ( images ) ;
}
}
}
// TODO modify proxy to serve images with CORS enabled, where available
function proxyGetImage ( url , img , imageObj ) {
var callback _name ,
scriptUrl = options . proxy ,
script ;
link . href = url ;
url = link . href ; // work around for pages with base href="" set - WARNING: this may change the url
callback _name = 'html2canvas_' + ( count ++ ) ;
imageObj . callbackname = callback _name ;
if ( scriptUrl . indexOf ( "?" ) > - 1 ) {
scriptUrl += "&" ;
} else {
scriptUrl += "?" ;
}
scriptUrl += 'url=' + encodeURIComponent ( url ) + '&callback=' + callback _name ;
script = doc . createElement ( "script" ) ;
window [ callback _name ] = function ( a ) {
if ( a . substring ( 0 , 6 ) === "error:" ) {
imageObj . succeeded = false ;
images . numLoaded ++ ;
images . numFailed ++ ;
start ( ) ;
} else {
setImageLoadHandlers ( img , imageObj ) ;
img . src = a ;
}
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)
try {
delete window [ callback _name ] ; // for all browser that support this
} catch ( ex ) { }
script . parentNode . removeChild ( script ) ;
script = null ;
delete imageObj . script ;
delete imageObj . callbackname ;
} ;
script . setAttribute ( "type" , "text/javascript" ) ;
script . setAttribute ( "src" , scriptUrl ) ;
imageObj . script = script ;
window . document . body . appendChild ( script ) ;
}
function loadPseudoElement ( element , type ) {
var style = document . defaultView . getComputedStyle ( element , type ) ;
if ( style != null ) {
var content = style . content ;
if ( typeof content != "undefined" ) {
if ( content . substr ( 0 , 3 ) === 'url' ) {
methods . loadImage ( _html2canvas . Util . parseBackgroundImage ( content ) [ 0 ] . args [ 0 ] ) ;
}
}
loadBackgroundImages ( style . backgroundImage , element ) ;
}
}
function loadPseudoElementImages ( element ) {
loadPseudoElement ( element , ":before" ) ;
loadPseudoElement ( element , ":after" ) ;
}
function loadGradientImage ( backgroundImage , bounds ) {
var img = _html2canvas . Generate . Gradient ( backgroundImage , bounds ) ;
if ( img !== undefined ) {
images [ backgroundImage ] = {
img : img ,
succeeded : true
} ;
images . numTotal ++ ;
images . numLoaded ++ ;
start ( ) ;
}
}
function invalidBackgrounds ( background _image ) {
return ( background _image && background _image . method && background _image . args && background _image . args . length > 0 ) ;
}
function loadBackgroundImages ( background _image , el ) {
var bounds ;
if ( typeof background _image == "undefined" )
return ;
_html2canvas . Util . parseBackgroundImage ( background _image ) . filter ( invalidBackgrounds ) . forEach ( function ( background _image ) {
if ( background _image . method === 'url' ) {
methods . loadImage ( background _image . args [ 0 ] ) ;
} else if ( background _image . method . match ( /\-?gradient$/ ) ) {
if ( bounds === undefined ) {
bounds = _html2canvas . Util . Bounds ( el ) ;
}
loadGradientImage ( background _image . value , bounds ) ;
}
} ) ;
}
function getImages ( el ) {
var elNodeType = false ;
// Firefox fails with permission denied on pages with iframes
try {
_html2canvas . Util . Children ( el ) . forEach ( function ( img ) {
getImages ( img ) ;
} ) ;
}
catch ( e ) { }
try {
elNodeType = el . nodeType ;
} catch ( ex ) {
elNodeType = false ;
h2clog ( "html2canvas: failed to access some element's nodeType - Exception: " + ex . message ) ;
}
if ( elNodeType === 1 || elNodeType === undefined ) {
loadPseudoElementImages ( el ) ;
try {
loadBackgroundImages ( _html2canvas . Util . getCSS ( el , 'backgroundImage' ) , el ) ;
} catch ( e ) {
h2clog ( "html2canvas: failed to get background-image - Exception: " + e . message ) ;
}
loadBackgroundImages ( el ) ;
}
}
function setImageLoadHandlers ( img , imageObj ) {
img . onload = function ( ) {
if ( imageObj . timer !== undefined ) {
// CORS succeeded
window . clearTimeout ( imageObj . timer ) ;
}
images . numLoaded ++ ;
imageObj . succeeded = true ;
img . onerror = img . onload = null ;
start ( ) ;
} ;
img . onerror = function ( ) {
if ( img . crossOrigin === "anonymous" ) {
// CORS failed
window . clearTimeout ( imageObj . timer ) ;
// let's try with proxy instead
if ( options . proxy ) {
var src = img . src ;
img = new Image ( ) ;
imageObj . img = img ;
img . src = src ;
proxyGetImage ( img . src , img , imageObj ) ;
return ;
}
}
images . numLoaded ++ ;
images . numFailed ++ ;
imageObj . succeeded = false ;
img . onerror = img . onload = null ;
start ( ) ;
} ;
}
methods = {
loadImage : function ( src ) {
var img , imageObj ;
if ( src && images [ src ] === undefined ) {
img = new Image ( ) ;
if ( src . match ( /data:image\/.*;base64,/i ) ) {
img . src = src . replace ( /url\(['"]{0,}|['"]{0,}\)$/ig , '' ) ;
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
} else if ( isSameOrigin ( src ) || options . allowTaint === true ) {
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
img . src = src ;
} else if ( supportCORS && ! options . allowTaint && options . useCORS ) {
// attempt to load with CORS
img . crossOrigin = "anonymous" ;
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
setImageLoadHandlers ( img , imageObj ) ;
img . src = src ;
// work around for https://bugs.webkit.org/show_bug.cgi?id=80028
img . customComplete = function ( ) {
if ( ! this . img . complete ) {
this . timer = window . setTimeout ( this . img . customComplete , 100 ) ;
} else {
this . img . onerror ( ) ;
}
} . bind ( imageObj ) ;
img . customComplete ( ) ;
} else if ( options . proxy ) {
imageObj = images [ src ] = {
img : img
} ;
images . numTotal ++ ;
proxyGetImage ( src , img , imageObj ) ;
}
}
} ,
cleanupDOM : function ( cause ) {
var img , src ;
if ( ! images . cleanupDone ) {
if ( cause && typeof cause === "string" ) {
h2clog ( "html2canvas: Cleanup because: " + cause ) ;
} else {
h2clog ( "html2canvas: Cleanup after timeout: " + options . timeout + " ms." ) ;
}
for ( src in images ) {
if ( images . hasOwnProperty ( src ) ) {
img = images [ src ] ;
if ( typeof img === "object" && img . callbackname && img . succeeded === undefined ) {
// cancel proxy image request
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)
try {
delete window [ img . callbackname ] ; // for all browser that support this
} catch ( ex ) { }
if ( img . script && img . script . parentNode ) {
img . script . setAttribute ( "src" , "about:blank" ) ; // try to cancel running request
img . script . parentNode . removeChild ( img . script ) ;
}
images . numLoaded ++ ;
images . numFailed ++ ;
h2clog ( "html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images . numLoaded + " / " + images . numTotal ) ;
}
}
}
// cancel any pending requests
if ( window . stop !== undefined ) {
window . stop ( ) ;
} else if ( document . execCommand !== undefined ) {
document . execCommand ( "Stop" , false ) ;
}
if ( document . close !== undefined ) {
document . close ( ) ;
}
images . cleanupDone = true ;
if ( ! ( cause && typeof cause === "string" ) ) {
start ( ) ;
}
}
} ,
renderingDone : function ( ) {
if ( timeoutTimer ) {
window . clearTimeout ( timeoutTimer ) ;
}
}
} ;
if ( options . timeout > 0 ) {
timeoutTimer = window . setTimeout ( methods . cleanupDOM , options . timeout ) ;
}
h2clog ( 'html2canvas: Preload starts: finding background-images' ) ;
images . firstRun = true ;
getImages ( element ) ;
h2clog ( 'html2canvas: Preload: Finding images' ) ;
// load <img> images
for ( i = 0 ; i < imgLen ; i += 1 ) {
methods . loadImage ( domImages [ i ] . getAttribute ( "src" ) ) ;
}
images . firstRun = false ;
h2clog ( 'html2canvas: Preload: Done.' ) ;
if ( images . numTotal === images . numLoaded ) {
start ( ) ;
}
return methods ;
} ;
_html2canvas . Renderer = function ( parseQueue , options ) {
function createRenderQueue ( parseQueue ) {
var queue = [ ] ;
var sortZ = function ( zStack ) {
var subStacks = [ ] ,
stackValues = [ ] ;
zStack . children . forEach ( function ( stackChild ) {
if ( stackChild . children && stackChild . children . length > 0 ) {
subStacks . push ( stackChild ) ;
stackValues . push ( stackChild . zindex ) ;
} else {
queue . push ( stackChild ) ;
}
} ) ;
stackValues . sort ( function ( a , b ) {
return a - b ;
} ) ;
stackValues . forEach ( function ( zValue ) {
var index ;
subStacks . some ( function ( stack , i ) {
index = i ;
return ( stack . zindex === zValue ) ;
} ) ;
sortZ ( subStacks . splice ( index , 1 ) [ 0 ] ) ;
} ) ;
} ;
sortZ ( parseQueue . zIndex ) ;
return queue ;
}
function getRenderer ( rendererName ) {
var renderer ;
if ( typeof options . renderer === "string" && _html2canvas . Renderer [ rendererName ] !== undefined ) {
renderer = _html2canvas . Renderer [ rendererName ] ( options ) ;
} else if ( typeof rendererName === "function" ) {
renderer = rendererName ( options ) ;
} else {
throw new Error ( "Unknown renderer" ) ;
}
if ( typeof renderer !== "function" ) {
throw new Error ( "Invalid renderer defined" ) ;
}
return renderer ;
}
return getRenderer ( options . renderer ) ( parseQueue , options , document , createRenderQueue ( parseQueue ) , _html2canvas ) ;
} ;
_html2canvas . Util . Support = function ( options , doc ) {
function supportSVGRendering ( ) {
var img = new Image ( ) ,
canvas = doc . createElement ( "canvas" ) ,
ctx = ( canvas . getContext === undefined ) ? false : canvas . getContext ( "2d" ) ;
if ( ctx === false ) {
return false ;
}
canvas . width = canvas . height = 10 ;
img . src = [
"data:image/svg+xml," ,
"<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>" ,
"<foreignObject width='10' height='10'>" ,
"<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>" ,
"sup" ,
"</div>" ,
"</foreignObject>" ,
"</svg>"
] . join ( "" ) ;
try {
ctx . drawImage ( img , 0 , 0 ) ;
canvas . toDataURL ( ) ;
} catch ( e ) {
return false ;
}
h2clog ( 'html2canvas: Parse: SVG powered rendering available' ) ;
return true ;
}
// Test whether we can use ranges to measure bounding boxes
// Opera doesn't provide valid bounds.height/bottom even though it supports the method.
function supportRangeBounds ( ) {
var r , testElement , rangeBounds , rangeHeight , support = false ;
if ( doc . createRange ) {
r = doc . createRange ( ) ;
if ( r . getBoundingClientRect ) {
testElement = doc . createElement ( 'boundtest' ) ;
testElement . style . height = "123px" ;
testElement . style . display = "block" ;
doc . body . appendChild ( testElement ) ;
r . selectNode ( testElement ) ;
rangeBounds = r . getBoundingClientRect ( ) ;
rangeHeight = rangeBounds . height ;
if ( rangeHeight === 123 ) {
support = true ;
}
doc . body . removeChild ( testElement ) ;
}
}
return support ;
}
return {
rangeBounds : supportRangeBounds ( ) ,
svgRendering : options . svgRendering && supportSVGRendering ( )
} ;
} ;
window . html2canvas = function ( elements , opts ) {
elements = ( elements . length ) ? elements : [ elements ] ;
var queue ,
canvas ,
options = {
// general
logging : false ,
elements : elements ,
background : "#fff" ,
// preload options
proxy : null ,
timeout : 0 , // no timeout
useCORS : false , // try to load images as CORS (where available), before falling back to proxy
allowTaint : false , // whether to allow images to taint the canvas, won't need proxy if set to true
// parse options
svgRendering : false , // use svg powered rendering where available (FF11+)
ignoreElements : "IFRAME|OBJECT|PARAM" ,
useOverflow : true ,
letterRendering : false ,
chinese : false ,
// render options
width : null ,
height : null ,
taintTest : true , // do a taint test with all images before applying to canvas
renderer : "Canvas"
} ;
options = _html2canvas . Util . Extend ( opts , options ) ;
_html2canvas . logging = options . logging ;
options . complete = function ( images ) {
if ( typeof options . onpreloaded === "function" ) {
if ( options . onpreloaded ( images ) === false ) {
return ;
}
}
queue = _html2canvas . Parse ( images , options ) ;
if ( typeof options . onparsed === "function" ) {
if ( options . onparsed ( queue ) === false ) {
return ;
}
}
canvas = _html2canvas . Renderer ( queue , options ) ;
if ( typeof options . onrendered === "function" ) {
options . onrendered ( canvas ) ;
}
} ;
// for pages without images, we still want this to be async, i.e. return methods before executing
window . setTimeout ( function ( ) {
_html2canvas . Preload ( options ) ;
} , 0 ) ;
return {
render : function ( queue , opts ) {
return _html2canvas . Renderer ( queue , _html2canvas . Util . Extend ( opts , options ) ) ;
} ,
parse : function ( images , opts ) {
return _html2canvas . Parse ( images , _html2canvas . Util . Extend ( opts , options ) ) ;
} ,
preload : function ( opts ) {
return _html2canvas . Preload ( _html2canvas . Util . Extend ( opts , options ) ) ;
} ,
log : h2clog
} ;
} ;
window . html2canvas . log = h2clog ; // for renderers
window . html2canvas . Renderer = {
Canvas : undefined // We are assuming this will be used
} ;
_html2canvas . Renderer . Canvas = function ( options ) {
options = options || { } ;
var doc = document ,
safeImages = [ ] ,
testCanvas = document . createElement ( "canvas" ) ,
testctx = testCanvas . getContext ( "2d" ) ,
canvas = options . canvas || doc . createElement ( 'canvas' ) ;
function createShape ( ctx , args ) {
ctx . beginPath ( ) ;
args . forEach ( function ( arg ) {
ctx [ arg . name ] . apply ( ctx , arg [ 'arguments' ] ) ;
} ) ;
ctx . closePath ( ) ;
}
function safeImage ( item ) {
if ( safeImages . indexOf ( item [ 'arguments' ] [ 0 ] . src ) === - 1 ) {
testctx . drawImage ( item [ 'arguments' ] [ 0 ] , 0 , 0 ) ;
try {
testctx . getImageData ( 0 , 0 , 1 , 1 ) ;
} catch ( e ) {
testCanvas = doc . createElement ( "canvas" ) ;
testctx = testCanvas . getContext ( "2d" ) ;
return false ;
}
safeImages . push ( item [ 'arguments' ] [ 0 ] . src ) ;
}
return true ;
}
function isTransparent ( backgroundColor ) {
return ( backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)" ) ;
}
function renderItem ( ctx , item ) {
switch ( item . type ) {
case "variable" :
ctx [ item . name ] = item [ 'arguments' ] ;
break ;
case "function" :
if ( item . name === "createPattern" ) {
if ( item [ 'arguments' ] [ 0 ] . width > 0 && item [ 'arguments' ] [ 0 ] . height > 0 ) {
try {
ctx . fillStyle = ctx . createPattern ( item [ 'arguments' ] [ 0 ] , "repeat" ) ;
}
catch ( e ) {
h2clog ( "html2canvas: Renderer: Error creating pattern" , e . message ) ;
}
}
} else if ( item . name === "drawShape" ) {
createShape ( ctx , item [ 'arguments' ] ) ;
} else if ( item . name === "drawImage" ) {
if ( item [ 'arguments' ] [ 8 ] > 0 && item [ 'arguments' ] [ 7 ] > 0 ) {
if ( ! options . taintTest || ( options . taintTest && safeImage ( item ) ) ) {
ctx . drawImage . apply ( ctx , item [ 'arguments' ] ) ;
}
}
} else {
ctx [ item . name ] . apply ( ctx , item [ 'arguments' ] ) ;
}
break ;
}
}
return function ( zStack , options , doc , queue , _html2canvas ) {
var ctx = canvas . getContext ( "2d" ) ,
storageContext ,
i ,
queueLen ,
newCanvas ,
bounds ,
fstyle ;
canvas . width = canvas . style . width = options . width || zStack . ctx . width ;
canvas . height = canvas . style . height = options . height || zStack . ctx . height ;
fstyle = ctx . fillStyle ;
ctx . fillStyle = ( isTransparent ( zStack . backgroundColor ) && options . background !== undefined ) ? options . background : zStack . backgroundColor ;
ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
ctx . fillStyle = fstyle ;
if ( options . svgRendering && zStack . svgRender !== undefined ) {
// TODO: enable async rendering to support this
ctx . drawImage ( zStack . svgRender , 0 , 0 ) ;
} else {
for ( i = 0 , queueLen = queue . length ; i < queueLen ; i += 1 ) {
storageContext = queue . splice ( 0 , 1 ) [ 0 ] ;
storageContext . canvasPosition = storageContext . canvasPosition || { } ;
// set common settings for canvas
ctx . textBaseline = "bottom" ;
if ( storageContext . clip ) {
ctx . save ( ) ;
ctx . beginPath ( ) ;
// console.log(storageContext);
ctx . rect ( storageContext . clip . left , storageContext . clip . top , storageContext . clip . width , storageContext . clip . height ) ;
ctx . clip ( ) ;
}
if ( storageContext . ctx . storage ) {
storageContext . ctx . storage . forEach ( renderItem . bind ( null , ctx ) ) ;
}
if ( storageContext . clip ) {
ctx . restore ( ) ;
}
}
}
h2clog ( "html2canvas: Renderer: Canvas renderer done - returning canvas obj" ) ;
queueLen = options . elements . length ;
if ( queueLen === 1 ) {
if ( typeof options . elements [ 0 ] === "object" && options . elements [ 0 ] . nodeName !== "BODY" ) {
// crop image to the bounds of selected (single) element
bounds = _html2canvas . Util . Bounds ( options . elements [ 0 ] ) ;
newCanvas = doc . createElement ( 'canvas' ) ;
newCanvas . width = bounds . width ;
newCanvas . height = bounds . height ;
ctx = newCanvas . getContext ( "2d" ) ;
ctx . drawImage ( canvas , bounds . left , bounds . top , bounds . width , bounds . height , 0 , 0 , bounds . width , bounds . height ) ;
canvas = null ;
return newCanvas ;
}
}
return canvas ;
} ;
} ;
} ) ( window , document ) ;