html2canvas.js 112 KB

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