Core.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. "use strict";
  2. var _html2canvas = {},
  3. previousElement,
  4. computedCSS,
  5. html2canvas;
  6. _html2canvas.Util = {};
  7. _html2canvas.Util.log = function(a) {
  8. if (_html2canvas.logging && window.console && window.console.log) {
  9. window.console.log(a);
  10. }
  11. };
  12. _html2canvas.Util.trimText = (function(isNative){
  13. return function(input) {
  14. return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
  15. };
  16. })(String.prototype.trim);
  17. _html2canvas.Util.asFloat = function(v) {
  18. return parseFloat(v);
  19. };
  20. (function() {
  21. // TODO: support all possible length values
  22. var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
  23. var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
  24. _html2canvas.Util.parseTextShadows = function (value) {
  25. if (!value || value === 'none') {
  26. return [];
  27. }
  28. // find multiple shadow declarations
  29. var shadows = value.match(TEXT_SHADOW_PROPERTY),
  30. results = [];
  31. for (var i = 0; shadows && (i < shadows.length); i++) {
  32. var s = shadows[i].match(TEXT_SHADOW_VALUES);
  33. results.push({
  34. color: s[0],
  35. offsetX: s[1] ? s[1].replace('px', '') : 0,
  36. offsetY: s[2] ? s[2].replace('px', '') : 0,
  37. blur: s[3] ? s[3].replace('px', '') : 0
  38. });
  39. }
  40. return results;
  41. };
  42. })();
  43. _html2canvas.Util.parseBackgroundImage = function (value) {
  44. var whitespace = ' \r\n\t',
  45. method, definition, prefix, prefix_i, block, results = [],
  46. c, mode = 0, numParen = 0, quote, args;
  47. var appendResult = function(){
  48. if(method) {
  49. if(definition.substr( 0, 1 ) === '"') {
  50. definition = definition.substr( 1, definition.length - 2 );
  51. }
  52. if(definition) {
  53. args.push(definition);
  54. }
  55. if(method.substr( 0, 1 ) === '-' &&
  56. (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) {
  57. prefix = method.substr( 0, prefix_i);
  58. method = method.substr( prefix_i );
  59. }
  60. results.push({
  61. prefix: prefix,
  62. method: method.toLowerCase(),
  63. value: block,
  64. args: args
  65. });
  66. }
  67. args = []; //for some odd reason, setting .length = 0 didn't work in safari
  68. method =
  69. prefix =
  70. definition =
  71. block = '';
  72. };
  73. appendResult();
  74. for(var i = 0, ii = value.length; i<ii; i++) {
  75. c = value[i];
  76. if(mode === 0 && whitespace.indexOf( c ) > -1){
  77. continue;
  78. }
  79. switch(c) {
  80. case '"':
  81. if(!quote) {
  82. quote = c;
  83. }
  84. else if(quote === c) {
  85. quote = null;
  86. }
  87. break;
  88. case '(':
  89. if(quote) { break; }
  90. else if(mode === 0) {
  91. mode = 1;
  92. block += c;
  93. continue;
  94. } else {
  95. numParen++;
  96. }
  97. break;
  98. case ')':
  99. if(quote) { break; }
  100. else if(mode === 1) {
  101. if(numParen === 0) {
  102. mode = 0;
  103. block += c;
  104. appendResult();
  105. continue;
  106. } else {
  107. numParen--;
  108. }
  109. }
  110. break;
  111. case ',':
  112. if(quote) { break; }
  113. else if(mode === 0) {
  114. appendResult();
  115. continue;
  116. }
  117. else if (mode === 1) {
  118. if(numParen === 0 && !method.match(/^url$/i)) {
  119. args.push(definition);
  120. definition = '';
  121. block += c;
  122. continue;
  123. }
  124. }
  125. break;
  126. }
  127. block += c;
  128. if(mode === 0) { method += c; }
  129. else { definition += c; }
  130. }
  131. appendResult();
  132. return results;
  133. };
  134. _html2canvas.Util.Bounds = function (element) {
  135. var clientRect, bounds = {};
  136. if (element.getBoundingClientRect){
  137. clientRect = element.getBoundingClientRect();
  138. // TODO add scroll position to bounds, so no scrolling of window necessary
  139. bounds.top = clientRect.top;
  140. bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
  141. bounds.left = clientRect.left;
  142. bounds.width = element.offsetWidth;
  143. bounds.height = element.offsetHeight;
  144. }
  145. return bounds;
  146. };
  147. // TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
  148. // but would require further work to calculate the correct positions for elements with offsetParents
  149. _html2canvas.Util.OffsetBounds = function (element) {
  150. var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
  151. return {
  152. top: element.offsetTop + parent.top,
  153. bottom: element.offsetTop + element.offsetHeight + parent.top,
  154. left: element.offsetLeft + parent.left,
  155. width: element.offsetWidth,
  156. height: element.offsetHeight
  157. };
  158. };
  159. function toPX(element, attribute, value ) {
  160. var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
  161. left,
  162. style = element.style;
  163. // Check if we are not dealing with pixels, (Opera has issues with this)
  164. // Ported from jQuery css.js
  165. // From the awesome hack by Dean Edwards
  166. // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
  167. // If we're not dealing with a regular pixel number
  168. // but a number that has a weird ending, we need to convert it to pixels
  169. if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) {
  170. // Remember the original values
  171. left = style.left;
  172. // Put in the new values to get a computed value out
  173. if (rsLeft) {
  174. element.runtimeStyle.left = element.currentStyle.left;
  175. }
  176. style.left = attribute === "fontSize" ? "1em" : (value || 0);
  177. value = style.pixelLeft + "px";
  178. // Revert the changed values
  179. style.left = left;
  180. if (rsLeft) {
  181. element.runtimeStyle.left = rsLeft;
  182. }
  183. }
  184. if (!/^(thin|medium|thick)$/i.test(value)) {
  185. return Math.round(parseFloat(value)) + "px";
  186. }
  187. return value;
  188. }
  189. function asInt(val) {
  190. return parseInt(val, 10);
  191. }
  192. function parseBackgroundSizePosition(value, element, attribute, index) {
  193. value = (value || '').split(',');
  194. value = value[index || 0] || value[0] || 'auto';
  195. value = _html2canvas.Util.trimText(value).split(' ');
  196. if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
  197. //these values will be handled in the parent function
  198. } else {
  199. value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
  200. if(value[1] === undefined) {
  201. if(attribute === 'backgroundSize') {
  202. value[1] = 'auto';
  203. return value;
  204. } else {
  205. // IE 9 doesn't return double digit always
  206. value[1] = value[0];
  207. }
  208. }
  209. value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
  210. }
  211. return value;
  212. }
  213. _html2canvas.Util.getCSS = function (element, attribute, index) {
  214. if (previousElement !== element) {
  215. computedCSS = document.defaultView.getComputedStyle(element, null);
  216. }
  217. var value = computedCSS[attribute];
  218. if (/^background(Size|Position)$/.test(attribute)) {
  219. return parseBackgroundSizePosition(value, element, attribute, index);
  220. } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
  221. var arr = value.split(" ");
  222. if (arr.length <= 1) {
  223. arr[1] = arr[0];
  224. }
  225. return arr.map(asInt);
  226. }
  227. return value;
  228. };
  229. _html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
  230. var target_ratio = target_width / target_height,
  231. current_ratio = current_width / current_height,
  232. output_width, output_height;
  233. if(!stretch_mode || stretch_mode === 'auto') {
  234. output_width = target_width;
  235. output_height = target_height;
  236. } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
  237. output_height = target_height;
  238. output_width = target_height * current_ratio;
  239. } else {
  240. output_width = target_width;
  241. output_height = target_width / current_ratio;
  242. }
  243. return {
  244. width: output_width,
  245. height: output_height
  246. };
  247. };
  248. function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) {
  249. var bgposition = _html2canvas.Util.getCSS( el, prop, imageIndex ) ,
  250. topPos,
  251. left,
  252. percentage,
  253. val;
  254. if (bgposition.length === 1){
  255. val = bgposition[0];
  256. bgposition = [];
  257. bgposition[0] = val;
  258. bgposition[1] = val;
  259. }
  260. if (bgposition[0].toString().indexOf("%") !== -1){
  261. percentage = (parseFloat(bgposition[0])/100);
  262. left = bounds.width * percentage;
  263. if(prop !== 'backgroundSize') {
  264. left -= (backgroundSize || image).width*percentage;
  265. }
  266. } else {
  267. if(prop === 'backgroundSize') {
  268. if(bgposition[0] === 'auto') {
  269. left = image.width;
  270. } else {
  271. if (/contain|cover/.test(bgposition[0])) {
  272. var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
  273. left = resized.width;
  274. topPos = resized.height;
  275. } else {
  276. left = parseInt(bgposition[0], 10);
  277. }
  278. }
  279. } else {
  280. left = parseInt( bgposition[0], 10);
  281. }
  282. }
  283. if(bgposition[1] === 'auto') {
  284. topPos = left / image.width * image.height;
  285. } else if (bgposition[1].toString().indexOf("%") !== -1){
  286. percentage = (parseFloat(bgposition[1])/100);
  287. topPos = bounds.height * percentage;
  288. if(prop !== 'backgroundSize') {
  289. topPos -= (backgroundSize || image).height * percentage;
  290. }
  291. } else {
  292. topPos = parseInt(bgposition[1],10);
  293. }
  294. return [left, topPos];
  295. }
  296. _html2canvas.Util.BackgroundPosition = function( el, bounds, image, imageIndex, backgroundSize ) {
  297. var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image, imageIndex, backgroundSize );
  298. return { left: result[0], top: result[1] };
  299. };
  300. _html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) {
  301. var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex );
  302. return { width: result[0], height: result[1] };
  303. };
  304. _html2canvas.Util.Extend = function (options, defaults) {
  305. for (var key in options) {
  306. if (options.hasOwnProperty(key)) {
  307. defaults[key] = options[key];
  308. }
  309. }
  310. return defaults;
  311. };
  312. /*
  313. * Derived from jQuery.contents()
  314. * Copyright 2010, John Resig
  315. * Dual licensed under the MIT or GPL Version 2 licenses.
  316. * http://jquery.org/license
  317. */
  318. _html2canvas.Util.Children = function( elem ) {
  319. var children;
  320. try {
  321. children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
  322. var ret = [];
  323. if (array !== null) {
  324. (function(first, second ) {
  325. var i = first.length,
  326. j = 0;
  327. if (typeof second.length === "number") {
  328. for (var l = second.length; j < l; j++) {
  329. first[i++] = second[j];
  330. }
  331. } else {
  332. while (second[j] !== undefined) {
  333. first[i++] = second[j++];
  334. }
  335. }
  336. first.length = i;
  337. return first;
  338. })(ret, array);
  339. }
  340. return ret;
  341. })(elem.childNodes);
  342. } catch (ex) {
  343. _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
  344. children = [];
  345. }
  346. return children;
  347. };
  348. _html2canvas.Util.isTransparent = function(backgroundColor) {
  349. return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
  350. };