You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

html2canvas.js 86 KiB

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