html2canvas.js 94 KB

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