Commit 94de9f484f51b5564caa2288358f7bc3e7a9fa5c

Authored by Edmar Moretti
1 parent 5ba7e71a

Inclusão da biblioteca tableexport

pacotes/tableexport/.gitignore 0 → 100755
... ... @@ -0,0 +1,11 @@
  1 +/nbproject/
  2 +image.jpg
  3 +/.project
  4 +/.settings/
  5 +node_modules/
  6 +.envrc
  7 +*.sublime-workspace
  8 +*.baseline
  9 +*.iml
  10 +.idea/
  11 +.DS_Store
... ...
pacotes/tableexport/.gitmodules 0 → 100755
... ... @@ -0,0 +1,3 @@
  1 +[submodule "src/fabric"]
  2 + path = src/fabric
  3 + url = https://github.com/kangax/fabric.js.git
... ...
pacotes/tableexport/.jshintrc 0 → 100755
... ... @@ -0,0 +1,19 @@
  1 +{
  2 + "curly": true,
  3 + "eqeqeq": true,
  4 + "immed": true,
  5 + "latedef": false,
  6 + "newcap": true,
  7 + "noarg": true,
  8 + "sub": true,
  9 + "undef": true,
  10 + "boss": true,
  11 + "eqnull": true,
  12 + "browser": true,
  13 + "node": true,
  14 + "indent": 4,
  15 + "globals": {
  16 + "jQuery": true
  17 + },
  18 + "predef": ["-Promise"]
  19 +}
... ...
pacotes/tableexport/.travis.yml 0 → 100755
... ... @@ -0,0 +1,21 @@
  1 +---
  2 +language: node_js
  3 +node_js:
  4 +- '0.10'
  5 +env:
  6 + global:
  7 + - secure: "eW41gIqOizwO4pTgWnAAbW75AP7F+CK9qfSed/fSh4sJ9HWMIY1YRIaY8gjr+6jV/f7XVHcXuym6ZxgINYSkVKbF1JKxBJNLOXtSgNbVHSic58pYFvUjwxIBI9aPig9uux1+DbnpWqXFDTcACJSevQZE0xwmjdrSkDLgB0G34v8="
  8 + - secure: "Y2Av+Gd3z9uQEB36GwdOOuGka0hx0/HeitASEo59z934O8RxnmN9eNTXS7dDT3XtKtwxIyLTOEpS7qlRdWahH28hr/dS4xJj6ao58C+1xMcDs6NAPGmDxUlcJWpcGEsnjmXjQCc3fBioSTdpIBrK/gdvgpNh77UKG74Sk7Z+YGk="
  9 + - secure: "YI+YbTOGf2x4fPMKW+KhJiZWswoXT6xOKGwLfsQsVwmFX1LerJouil5D5iYOQuL4FE3pNaoJSNakIsokJQuGKJMmnPc8rdhMZuBJBk6MRghurE2Xe9qBHfuUBPlfD61nARESm4WDcyMwM0QVYaOKeY6aIpZ91qbUbyc60EEx3C4="
  10 +addons:
  11 + sauce_connect: true
  12 +before_script:
  13 + - npm install -g grunt-cli
  14 + - npm install -g uglify-js
  15 +notifications:
  16 + webhooks:
  17 + urls:
  18 + - https://webhooks.gitter.im/e/2b007d4f86de89588804
  19 + on_success: always
  20 + on_failure: always
  21 + on_start: false
... ...
pacotes/tableexport/README.md 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +tableExport.jquery.plugin
  2 +=========================
  3 +
  4 +<h3>Export HTML Table to</h3>
  5 +<ul>
  6 +<li> JSON
  7 +<li> XML
  8 +<li> PNG
  9 +<li> CSV
  10 +<li> TXT
  11 +<li> SQL
  12 +<li> MS-Word
  13 +<li> Ms-Excel
  14 +<li> Ms-Powerpoint
  15 +<li> PDF
  16 +</ul>
  17 +
  18 +Installation
  19 +============
  20 +jquery Plugin<BR>
  21 +&lt;script type="text/javascript" src="tableExport.js"><BR>
  22 +&lt;script type="text/javascript" src="jquery.base64.js"><BR>
  23 +<BR>
  24 +PNG Export
  25 +==========
  26 +&lt;script type="text/javascript" src="html2canvas.js">
  27 +
  28 +PDF Export
  29 +==========
  30 +&lt;script type="text/javascript" src="jspdf/libs/sprintf.js"><BR>
  31 +&lt;script type="text/javascript" src="jspdf/jspdf.js"><BR>
  32 +&lt;script type="text/javascript" src="jspdf/libs/base64.js"><BR>
  33 +
  34 +Usage
  35 +======
  36 +onClick ="$('#tableID').tableExport({type:'pdf',escape:'false'});"<BR>
  37 +
  38 +Options
  39 +=======
  40 +separator: ','<BR>
  41 +ignoreColumn: [2,3],<BR>
  42 +tableName:'yourTableName'<BR>
  43 +type:'csv'<BR>
  44 +pdfFontSize:14<BR>
  45 +pdfLeftMargin:20<BR>
  46 +escape:'true'<BR>
  47 +htmlContent:'false'<BR>
  48 +consoleLog:'false' <BR>
... ...
pacotes/tableexport/html2canvas.js 0 → 100644
... ... @@ -0,0 +1,3010 @@
  1 +/*
  2 + html2canvas 0.4.1 <http://html2canvas.hertzen.com>
  3 + Copyright (c) 2013 Niklas von Hertzen
  4 +
  5 + Released under MIT License
  6 +*/
  7 +
  8 +(function(window, document, undefined){
  9 +
  10 +//"use strict";
  11 +
  12 +var _html2canvas = {},
  13 +previousElement,
  14 +computedCSS,
  15 +html2canvas;
  16 +
  17 +_html2canvas.Util = {};
  18 +
  19 +_html2canvas.Util.log = function(a) {
  20 + if (_html2canvas.logging && window.console && window.console.log) {
  21 + window.console.log(a);
  22 + }
  23 +};
  24 +
  25 +_html2canvas.Util.trimText = (function(isNative){
  26 + return function(input) {
  27 + return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
  28 + };
  29 +})(String.prototype.trim);
  30 +
  31 +_html2canvas.Util.asFloat = function(v) {
  32 + return parseFloat(v);
  33 +};
  34 +
  35 +(function() {
  36 + // TODO: support all possible length values
  37 + var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
  38 + var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
  39 + _html2canvas.Util.parseTextShadows = function (value) {
  40 + if (!value || value === 'none') {
  41 + return [];
  42 + }
  43 +
  44 + // find multiple shadow declarations
  45 + var shadows = value.match(TEXT_SHADOW_PROPERTY),
  46 + results = [];
  47 + for (var i = 0; shadows && (i < shadows.length); i++) {
  48 + var s = shadows[i].match(TEXT_SHADOW_VALUES);
  49 + results.push({
  50 + color: s[0],
  51 + offsetX: s[1] ? s[1].replace('px', '') : 0,
  52 + offsetY: s[2] ? s[2].replace('px', '') : 0,
  53 + blur: s[3] ? s[3].replace('px', '') : 0
  54 + });
  55 + }
  56 + return results;
  57 + };
  58 +})();
  59 +
  60 +_html2canvas.Util.parseBackgroundImage = function (value) {
  61 + var whitespace = ' \r\n\t',
  62 + method, definition, prefix, prefix_i, block, results = [],
  63 + c, mode = 0, numParen = 0, quote, args;
  64 +
  65 + var appendResult = function(){
  66 + if(method) {
  67 + if(definition.substr( 0, 1 ) === '"') {
  68 + definition = definition.substr( 1, definition.length - 2 );
  69 + }
  70 + if(definition) {
  71 + args.push(definition);
  72 + }
  73 + if(method.substr( 0, 1 ) === '-' &&
  74 + (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) {
  75 + prefix = method.substr( 0, prefix_i);
  76 + method = method.substr( prefix_i );
  77 + }
  78 + results.push({
  79 + prefix: prefix,
  80 + method: method.toLowerCase(),
  81 + value: block,
  82 + args: args
  83 + });
  84 + }
  85 + args = []; //for some odd reason, setting .length = 0 didn't work in safari
  86 + method =
  87 + prefix =
  88 + definition =
  89 + block = '';
  90 + };
  91 +
  92 + appendResult();
  93 + for(var i = 0, ii = value.length; i<ii; i++) {
  94 + c = value[i];
  95 + if(mode === 0 && whitespace.indexOf( c ) > -1){
  96 + continue;
  97 + }
  98 + switch(c) {
  99 + case '"':
  100 + if(!quote) {
  101 + quote = c;
  102 + }
  103 + else if(quote === c) {
  104 + quote = null;
  105 + }
  106 + break;
  107 +
  108 + case '(':
  109 + if(quote) { break; }
  110 + else if(mode === 0) {
  111 + mode = 1;
  112 + block += c;
  113 + continue;
  114 + } else {
  115 + numParen++;
  116 + }
  117 + break;
  118 +
  119 + case ')':
  120 + if(quote) { break; }
  121 + else if(mode === 1) {
  122 + if(numParen === 0) {
  123 + mode = 0;
  124 + block += c;
  125 + appendResult();
  126 + continue;
  127 + } else {
  128 + numParen--;
  129 + }
  130 + }
  131 + break;
  132 +
  133 + case ',':
  134 + if(quote) { break; }
  135 + else if(mode === 0) {
  136 + appendResult();
  137 + continue;
  138 + }
  139 + else if (mode === 1) {
  140 + if(numParen === 0 && !method.match(/^url$/i)) {
  141 + args.push(definition);
  142 + definition = '';
  143 + block += c;
  144 + continue;
  145 + }
  146 + }
  147 + break;
  148 + }
  149 +
  150 + block += c;
  151 + if(mode === 0) { method += c; }
  152 + else { definition += c; }
  153 + }
  154 + appendResult();
  155 +
  156 + return results;
  157 +};
  158 +
  159 +_html2canvas.Util.Bounds = function (element) {
  160 + var clientRect, bounds = {};
  161 +
  162 + if (element.getBoundingClientRect){
  163 + clientRect = element.getBoundingClientRect();
  164 +
  165 + // TODO add scroll position to bounds, so no scrolling of window necessary
  166 + bounds.top = clientRect.top;
  167 + bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
  168 + bounds.left = clientRect.left;
  169 +
  170 + bounds.width = element.offsetWidth;
  171 + bounds.height = element.offsetHeight;
  172 + }
  173 +
  174 + return bounds;
  175 +};
  176 +
  177 +// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
  178 +// but would require further work to calculate the correct positions for elements with offsetParents
  179 +_html2canvas.Util.OffsetBounds = function (element) {
  180 + var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
  181 +
  182 + return {
  183 + top: element.offsetTop + parent.top,
  184 + bottom: element.offsetTop + element.offsetHeight + parent.top,
  185 + left: element.offsetLeft + parent.left,
  186 + width: element.offsetWidth,
  187 + height: element.offsetHeight
  188 + };
  189 +};
  190 +
  191 +function toPX(element, attribute, value ) {
  192 + var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
  193 + left,
  194 + style = element.style;
  195 +
  196 + // Check if we are not dealing with pixels, (Opera has issues with this)
  197 + // Ported from jQuery css.js
  198 + // From the awesome hack by Dean Edwards
  199 + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
  200 +
  201 + // If we're not dealing with a regular pixel number
  202 + // but a number that has a weird ending, we need to convert it to pixels
  203 +
  204 + if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) {
  205 + // Remember the original values
  206 + left = style.left;
  207 +
  208 + // Put in the new values to get a computed value out
  209 + if (rsLeft) {
  210 + element.runtimeStyle.left = element.currentStyle.left;
  211 + }
  212 + style.left = attribute === "fontSize" ? "1em" : (value || 0);
  213 + value = style.pixelLeft + "px";
  214 +
  215 + // Revert the changed values
  216 + style.left = left;
  217 + if (rsLeft) {
  218 + element.runtimeStyle.left = rsLeft;
  219 + }
  220 + }
  221 +
  222 + if (!/^(thin|medium|thick)$/i.test(value)) {
  223 + return Math.round(parseFloat(value)) + "px";
  224 + }
  225 +
  226 + return value;
  227 +}
  228 +
  229 +function asInt(val) {
  230 + return parseInt(val, 10);
  231 +}
  232 +
  233 +function isPercentage(value) {
  234 + return value.toString().indexOf("%") !== -1;
  235 +}
  236 +
  237 +function parseBackgroundSizePosition(value, element, attribute, index) {
  238 + value = (value || '').split(',');
  239 + value = value[index || 0] || value[0] || 'auto';
  240 + value = _html2canvas.Util.trimText(value).split(' ');
  241 + if(attribute === 'backgroundSize' && (value[0] && value[0].match(/^(cover|contain|auto)$/))) {
  242 + return value;
  243 + } else {
  244 + value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
  245 + if(value[1] === undefined) {
  246 + if(attribute === 'backgroundSize') {
  247 + value[1] = 'auto';
  248 + return value;
  249 + } else {
  250 + // IE 9 doesn't return double digit always
  251 + value[1] = value[0];
  252 + }
  253 + }
  254 + value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
  255 + }
  256 + return value;
  257 +}
  258 +
  259 +_html2canvas.Util.getCSS = function (element, attribute, index) {
  260 + if (previousElement !== element) {
  261 + computedCSS = document.defaultView.getComputedStyle(element, null);
  262 + }
  263 +
  264 + var value = computedCSS[attribute];
  265 +
  266 + if (/^background(Size|Position)$/.test(attribute)) {
  267 + return parseBackgroundSizePosition(value, element, attribute, index);
  268 + } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
  269 + var arr = value.split(" ");
  270 + if (arr.length <= 1) {
  271 + arr[1] = arr[0];
  272 + }
  273 + return arr.map(asInt);
  274 + }
  275 +
  276 + return value;
  277 +};
  278 +
  279 +_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
  280 + var target_ratio = target_width / target_height,
  281 + current_ratio = current_width / current_height,
  282 + output_width, output_height;
  283 +
  284 + if(!stretch_mode || stretch_mode === 'auto') {
  285 + output_width = target_width;
  286 + output_height = target_height;
  287 + } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
  288 + output_height = target_height;
  289 + output_width = target_height * current_ratio;
  290 + } else {
  291 + output_width = target_width;
  292 + output_height = target_width / current_ratio;
  293 + }
  294 +
  295 + return {
  296 + width: output_width,
  297 + height: output_height
  298 + };
  299 +};
  300 +
  301 +_html2canvas.Util.BackgroundPosition = function(element, bounds, image, imageIndex, backgroundSize ) {
  302 + var backgroundPosition = _html2canvas.Util.getCSS(element, 'backgroundPosition', imageIndex),
  303 + leftPosition,
  304 + topPosition;
  305 + if (backgroundPosition.length === 1){
  306 + backgroundPosition = [backgroundPosition[0], backgroundPosition[0]];
  307 + }
  308 +
  309 + if (isPercentage(backgroundPosition[0])){
  310 + leftPosition = (bounds.width - (backgroundSize || image).width) * (parseFloat(backgroundPosition[0]) / 100);
  311 + } else {
  312 + leftPosition = parseInt(backgroundPosition[0], 10);
  313 + }
  314 +
  315 + if (backgroundPosition[1] === 'auto') {
  316 + topPosition = leftPosition / image.width * image.height;
  317 + } else if (isPercentage(backgroundPosition[1])){
  318 + topPosition = (bounds.height - (backgroundSize || image).height) * parseFloat(backgroundPosition[1]) / 100;
  319 + } else {
  320 + topPosition = parseInt(backgroundPosition[1], 10);
  321 + }
  322 +
  323 + if (backgroundPosition[0] === 'auto') {
  324 + leftPosition = topPosition / image.height * image.width;
  325 + }
  326 +
  327 + return {left: leftPosition, top: topPosition};
  328 +};
  329 +
  330 +_html2canvas.Util.BackgroundSize = function(element, bounds, image, imageIndex) {
  331 + var backgroundSize = _html2canvas.Util.getCSS(element, 'backgroundSize', imageIndex), width, height;
  332 +
  333 + if (backgroundSize.length === 1) {
  334 + backgroundSize = [backgroundSize[0], backgroundSize[0]];
  335 + }
  336 +
  337 + if (isPercentage(backgroundSize[0])) {
  338 + width = bounds.width * parseFloat(backgroundSize[0]) / 100;
  339 + } else if (/contain|cover/.test(backgroundSize[0])) {
  340 + return _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, backgroundSize[0]);
  341 + } else {
  342 + width = parseInt(backgroundSize[0], 10);
  343 + }
  344 +
  345 + if (backgroundSize[0] === 'auto' && backgroundSize[1] === 'auto') {
  346 + height = image.height;
  347 + } else if (backgroundSize[1] === 'auto') {
  348 + height = width / image.width * image.height;
  349 + } else if (isPercentage(backgroundSize[1])) {
  350 + height = bounds.height * parseFloat(backgroundSize[1]) / 100;
  351 + } else {
  352 + height = parseInt(backgroundSize[1], 10);
  353 + }
  354 +
  355 + if (backgroundSize[0] === 'auto') {
  356 + width = height / image.height * image.width;
  357 + }
  358 +
  359 + return {width: width, height: height};
  360 +};
  361 +
  362 +_html2canvas.Util.BackgroundRepeat = function(element, imageIndex) {
  363 + var backgroundRepeat = _html2canvas.Util.getCSS(element, "backgroundRepeat").split(",").map(_html2canvas.Util.trimText);
  364 + return backgroundRepeat[imageIndex] || backgroundRepeat[0];
  365 +};
  366 +
  367 +_html2canvas.Util.Extend = function (options, defaults) {
  368 + for (var key in options) {
  369 + if (options.hasOwnProperty(key)) {
  370 + defaults[key] = options[key];
  371 + }
  372 + }
  373 + return defaults;
  374 +};
  375 +
  376 +
  377 +/*
  378 + * Derived from jQuery.contents()
  379 + * Copyright 2010, John Resig
  380 + * Dual licensed under the MIT or GPL Version 2 licenses.
  381 + * http://jquery.org/license
  382 + */
  383 +_html2canvas.Util.Children = function( elem ) {
  384 + var children;
  385 + try {
  386 + children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
  387 + var ret = [];
  388 + if (array !== null) {
  389 + (function(first, second ) {
  390 + var i = first.length,
  391 + j = 0;
  392 +
  393 + if (typeof second.length === "number") {
  394 + for (var l = second.length; j < l; j++) {
  395 + first[i++] = second[j];
  396 + }
  397 + } else {
  398 + while (second[j] !== undefined) {
  399 + first[i++] = second[j++];
  400 + }
  401 + }
  402 +
  403 + first.length = i;
  404 +
  405 + return first;
  406 + })(ret, array);
  407 + }
  408 + return ret;
  409 + })(elem.childNodes);
  410 +
  411 + } catch (ex) {
  412 + _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
  413 + children = [];
  414 + }
  415 + return children;
  416 +};
  417 +
  418 +_html2canvas.Util.isTransparent = function(backgroundColor) {
  419 + return (!backgroundColor || backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
  420 +};
  421 +
  422 +_html2canvas.Util.Font = (function () {
  423 +
  424 + var fontData = {};
  425 +
  426 + return function(font, fontSize, doc) {
  427 + if (fontData[font + "-" + fontSize] !== undefined) {
  428 + return fontData[font + "-" + fontSize];
  429 + }
  430 +
  431 + var container = doc.createElement('div'),
  432 + img = doc.createElement('img'),
  433 + span = doc.createElement('span'),
  434 + sampleText = 'Hidden Text',
  435 + baseline,
  436 + middle,
  437 + metricsObj;
  438 +
  439 + container.style.visibility = "hidden";
  440 + container.style.fontFamily = font;
  441 + container.style.fontSize = fontSize;
  442 + container.style.margin = 0;
  443 + container.style.padding = 0;
  444 +
  445 + doc.body.appendChild(container);
  446 +
  447 + // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
  448 + img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=";
  449 + img.width = 1;
  450 + img.height = 1;
  451 +
  452 + img.style.margin = 0;
  453 + img.style.padding = 0;
  454 + img.style.verticalAlign = "baseline";
  455 +
  456 + span.style.fontFamily = font;
  457 + span.style.fontSize = fontSize;
  458 + span.style.margin = 0;
  459 + span.style.padding = 0;
  460 +
  461 + span.appendChild(doc.createTextNode(sampleText));
  462 + container.appendChild(span);
  463 + container.appendChild(img);
  464 + baseline = (img.offsetTop - span.offsetTop) + 1;
  465 +
  466 + container.removeChild(span);
  467 + container.appendChild(doc.createTextNode(sampleText));
  468 +
  469 + container.style.lineHeight = "normal";
  470 + img.style.verticalAlign = "super";
  471 +
  472 + middle = (img.offsetTop-container.offsetTop) + 1;
  473 + metricsObj = {
  474 + baseline: baseline,
  475 + lineWidth: 1,
  476 + middle: middle
  477 + };
  478 +
  479 + fontData[font + "-" + fontSize] = metricsObj;
  480 +
  481 + doc.body.removeChild(container);
  482 +
  483 + return metricsObj;
  484 + };
  485 +})();
  486 +
  487 +(function(){
  488 + var Util = _html2canvas.Util,
  489 + Generate = {};
  490 +
  491 + _html2canvas.Generate = Generate;
  492 +
  493 + var reGradients = [
  494 + /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
  495 + /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
  496 + /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
  497 + /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
  498 + /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
  499 + /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
  500 + /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
  501 + ];
  502 +
  503 + /*
  504 + * TODO: Add IE10 vendor prefix (-ms) support
  505 + * TODO: Add W3C gradient (linear-gradient) support
  506 + * TODO: Add old Webkit -webkit-gradient(radial, ...) support
  507 + * TODO: Maybe some RegExp optimizations are possible ;o)
  508 + */
  509 + Generate.parseGradient = function(css, bounds) {
  510 + var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl;
  511 +
  512 + for(i = 0; i < len; i+=1){
  513 + m1 = css.match(reGradients[i]);
  514 + if(m1) {
  515 + break;
  516 + }
  517 + }
  518 +
  519 + if(m1) {
  520 + switch(m1[1]) {
  521 + case '-webkit-linear-gradient':
  522 + case '-o-linear-gradient':
  523 +
  524 + gradient = {
  525 + type: 'linear',
  526 + x0: null,
  527 + y0: null,
  528 + x1: null,
  529 + y1: null,
  530 + colorStops: []
  531 + };
  532 +
  533 + // get coordinates
  534 + m2 = m1[2].match(/\w+/g);
  535 + if(m2){
  536 + m2Len = m2.length;
  537 + for(i = 0; i < m2Len; i+=1){
  538 + switch(m2[i]) {
  539 + case 'top':
  540 + gradient.y0 = 0;
  541 + gradient.y1 = bounds.height;
  542 + break;
  543 +
  544 + case 'right':
  545 + gradient.x0 = bounds.width;
  546 + gradient.x1 = 0;
  547 + break;
  548 +
  549 + case 'bottom':
  550 + gradient.y0 = bounds.height;
  551 + gradient.y1 = 0;
  552 + break;
  553 +
  554 + case 'left':
  555 + gradient.x0 = 0;
  556 + gradient.x1 = bounds.width;
  557 + break;
  558 + }
  559 + }
  560 + }
  561 + if(gradient.x0 === null && gradient.x1 === null){ // center
  562 + gradient.x0 = gradient.x1 = bounds.width / 2;
  563 + }
  564 + if(gradient.y0 === null && gradient.y1 === null){ // center
  565 + gradient.y0 = gradient.y1 = bounds.height / 2;
  566 + }
  567 +
  568 + // get colors and stops
  569 + 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);
  570 + if(m2){
  571 + m2Len = m2.length;
  572 + step = 1 / Math.max(m2Len - 1, 1);
  573 + for(i = 0; i < m2Len; i+=1){
  574 + 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)?/);
  575 + if(m3[2]){
  576 + stop = parseFloat(m3[2]);
  577 + if(m3[3] === '%'){
  578 + stop /= 100;
  579 + } else { // px - stupid opera
  580 + stop /= bounds.width;
  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 +
  593 + case '-webkit-gradient':
  594 +
  595 + gradient = {
  596 + type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
  597 + x0: 0,
  598 + y0: 0,
  599 + x1: 0,
  600 + y1: 0,
  601 + colorStops: []
  602 + };
  603 +
  604 + // get coordinates
  605 + m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
  606 + if(m2){
  607 + gradient.x0 = (m2[1] * bounds.width) / 100;
  608 + gradient.y0 = (m2[2] * bounds.height) / 100;
  609 + gradient.x1 = (m2[3] * bounds.width) / 100;
  610 + gradient.y1 = (m2[4] * bounds.height) / 100;
  611 + }
  612 +
  613 + // get colors and stops
  614 + 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);
  615 + if(m2){
  616 + m2Len = m2.length;
  617 + for(i = 0; i < m2Len; i+=1){
  618 + 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\.]+)?\))\)/);
  619 + stop = parseFloat(m3[2]);
  620 + if(m3[1] === 'from') {
  621 + stop = 0.0;
  622 + }
  623 + if(m3[1] === 'to') {
  624 + stop = 1.0;
  625 + }
  626 + gradient.colorStops.push({
  627 + color: m3[3],
  628 + stop: stop
  629 + });
  630 + }
  631 + }
  632 + break;
  633 +
  634 + case '-moz-linear-gradient':
  635 +
  636 + gradient = {
  637 + type: 'linear',
  638 + x0: 0,
  639 + y0: 0,
  640 + x1: 0,
  641 + y1: 0,
  642 + colorStops: []
  643 + };
  644 +
  645 + // get coordinates
  646 + m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
  647 +
  648 + // m2[1] == 0% -> left
  649 + // m2[1] == 50% -> center
  650 + // m2[1] == 100% -> right
  651 +
  652 + // m2[2] == 0% -> top
  653 + // m2[2] == 50% -> center
  654 + // m2[2] == 100% -> bottom
  655 +
  656 + if(m2){
  657 + gradient.x0 = (m2[1] * bounds.width) / 100;
  658 + gradient.y0 = (m2[2] * bounds.height) / 100;
  659 + gradient.x1 = bounds.width - gradient.x0;
  660 + gradient.y1 = bounds.height - gradient.y0;
  661 + }
  662 +
  663 + // get colors and stops
  664 + 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);
  665 + if(m2){
  666 + m2Len = m2.length;
  667 + step = 1 / Math.max(m2Len - 1, 1);
  668 + for(i = 0; i < m2Len; i+=1){
  669 + m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
  670 + if(m3[2]){
  671 + stop = parseFloat(m3[2]);
  672 + if(m3[3]){ // percentage
  673 + stop /= 100;
  674 + }
  675 + } else {
  676 + stop = i * step;
  677 + }
  678 + gradient.colorStops.push({
  679 + color: m3[1],
  680 + stop: stop
  681 + });
  682 + }
  683 + }
  684 + break;
  685 +
  686 + case '-webkit-radial-gradient':
  687 + case '-moz-radial-gradient':
  688 + case '-o-radial-gradient':
  689 +
  690 + gradient = {
  691 + type: 'circle',
  692 + x0: 0,
  693 + y0: 0,
  694 + x1: bounds.width,
  695 + y1: bounds.height,
  696 + cx: 0,
  697 + cy: 0,
  698 + rx: 0,
  699 + ry: 0,
  700 + colorStops: []
  701 + };
  702 +
  703 + // center
  704 + m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
  705 + if(m2){
  706 + gradient.cx = (m2[1] * bounds.width) / 100;
  707 + gradient.cy = (m2[2] * bounds.height) / 100;
  708 + }
  709 +
  710 + // size
  711 + m2 = m1[3].match(/\w+/);
  712 + m3 = m1[4].match(/[a-z\-]*/);
  713 + if(m2 && m3){
  714 + switch(m3[0]){
  715 + case 'farthest-corner':
  716 + case 'cover': // is equivalent to farthest-corner
  717 + case '': // mozilla removes "cover" from definition :(
  718 + tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
  719 + tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
  720 + br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
  721 + bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
  722 + gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
  723 + break;
  724 + case 'closest-corner':
  725 + tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
  726 + tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
  727 + br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
  728 + bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
  729 + gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
  730 + break;
  731 + case 'farthest-side':
  732 + if(m2[0] === 'circle'){
  733 + gradient.rx = gradient.ry = Math.max(
  734 + gradient.cx,
  735 + gradient.cy,
  736 + gradient.x1 - gradient.cx,
  737 + gradient.y1 - gradient.cy
  738 + );
  739 + } else { // ellipse
  740 +
  741 + gradient.type = m2[0];
  742 +
  743 + gradient.rx = Math.max(
  744 + gradient.cx,
  745 + gradient.x1 - gradient.cx
  746 + );
  747 + gradient.ry = Math.max(
  748 + gradient.cy,
  749 + gradient.y1 - gradient.cy
  750 + );
  751 + }
  752 + break;
  753 + case 'closest-side':
  754 + case 'contain': // is equivalent to closest-side
  755 + if(m2[0] === 'circle'){
  756 + gradient.rx = gradient.ry = Math.min(
  757 + gradient.cx,
  758 + gradient.cy,
  759 + gradient.x1 - gradient.cx,
  760 + gradient.y1 - gradient.cy
  761 + );
  762 + } else { // ellipse
  763 +
  764 + gradient.type = m2[0];
  765 +
  766 + gradient.rx = Math.min(
  767 + gradient.cx,
  768 + gradient.x1 - gradient.cx
  769 + );
  770 + gradient.ry = Math.min(
  771 + gradient.cy,
  772 + gradient.y1 - gradient.cy
  773 + );
  774 + }
  775 + break;
  776 +
  777 + // TODO: add support for "30px 40px" sizes (webkit only)
  778 + }
  779 + }
  780 +
  781 + // color stops
  782 + 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);
  783 + if(m2){
  784 + m2Len = m2.length;
  785 + step = 1 / Math.max(m2Len - 1, 1);
  786 + for(i = 0; i < m2Len; i+=1){
  787 + 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)?/);
  788 + if(m3[2]){
  789 + stop = parseFloat(m3[2]);
  790 + if(m3[3] === '%'){
  791 + stop /= 100;
  792 + } else { // px - stupid opera
  793 + stop /= bounds.width;
  794 + }
  795 + } else {
  796 + stop = i * step;
  797 + }
  798 + gradient.colorStops.push({
  799 + color: m3[1],
  800 + stop: stop
  801 + });
  802 + }
  803 + }
  804 + break;
  805 + }
  806 + }
  807 +
  808 + return gradient;
  809 + };
  810 +
  811 + function addScrollStops(grad) {
  812 + return function(colorStop) {
  813 + try {
  814 + grad.addColorStop(colorStop.stop, colorStop.color);
  815 + }
  816 + catch(e) {
  817 + Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
  818 + }
  819 + };
  820 + }
  821 +
  822 + Generate.Gradient = function(src, bounds) {
  823 + if(bounds.width === 0 || bounds.height === 0) {
  824 + return;
  825 + }
  826 +
  827 + var canvas = document.createElement('canvas'),
  828 + ctx = canvas.getContext('2d'),
  829 + gradient, grad;
  830 +
  831 + canvas.width = bounds.width;
  832 + canvas.height = bounds.height;
  833 +
  834 + // TODO: add support for multi defined background gradients
  835 + gradient = _html2canvas.Generate.parseGradient(src, bounds);
  836 +
  837 + if(gradient) {
  838 + switch(gradient.type) {
  839 + case 'linear':
  840 + grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
  841 + gradient.colorStops.forEach(addScrollStops(grad));
  842 + ctx.fillStyle = grad;
  843 + ctx.fillRect(0, 0, bounds.width, bounds.height);
  844 + break;
  845 +
  846 + case 'circle':
  847 + grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
  848 + gradient.colorStops.forEach(addScrollStops(grad));
  849 + ctx.fillStyle = grad;
  850 + ctx.fillRect(0, 0, bounds.width, bounds.height);
  851 + break;
  852 +
  853 + case 'ellipse':
  854 + var canvasRadial = document.createElement('canvas'),
  855 + ctxRadial = canvasRadial.getContext('2d'),
  856 + ri = Math.max(gradient.rx, gradient.ry),
  857 + di = ri * 2;
  858 +
  859 + canvasRadial.width = canvasRadial.height = di;
  860 +
  861 + grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
  862 + gradient.colorStops.forEach(addScrollStops(grad));
  863 +
  864 + ctxRadial.fillStyle = grad;
  865 + ctxRadial.fillRect(0, 0, di, di);
  866 +
  867 + ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
  868 + ctx.fillRect(0, 0, canvas.width, canvas.height);
  869 + ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
  870 + break;
  871 + }
  872 + }
  873 +
  874 + return canvas;
  875 + };
  876 +
  877 + Generate.ListAlpha = function(number) {
  878 + var tmp = "",
  879 + modulus;
  880 +
  881 + do {
  882 + modulus = number % 26;
  883 + tmp = String.fromCharCode((modulus) + 64) + tmp;
  884 + number = number / 26;
  885 + }while((number*26) > 26);
  886 +
  887 + return tmp;
  888 + };
  889 +
  890 + Generate.ListRoman = function(number) {
  891 + var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
  892 + decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
  893 + roman = "",
  894 + v,
  895 + len = romanArray.length;
  896 +
  897 + if (number <= 0 || number >= 4000) {
  898 + return number;
  899 + }
  900 +
  901 + for (v=0; v < len; v+=1) {
  902 + while (number >= decimal[v]) {
  903 + number -= decimal[v];
  904 + roman += romanArray[v];
  905 + }
  906 + }
  907 +
  908 + return roman;
  909 + };
  910 +})();
  911 +function h2cRenderContext(width, height) {
  912 + var storage = [];
  913 + return {
  914 + storage: storage,
  915 + width: width,
  916 + height: height,
  917 + clip: function() {
  918 + storage.push({
  919 + type: "function",
  920 + name: "clip",
  921 + 'arguments': arguments
  922 + });
  923 + },
  924 + translate: function() {
  925 + storage.push({
  926 + type: "function",
  927 + name: "translate",
  928 + 'arguments': arguments
  929 + });
  930 + },
  931 + fill: function() {
  932 + storage.push({
  933 + type: "function",
  934 + name: "fill",
  935 + 'arguments': arguments
  936 + });
  937 + },
  938 + save: function() {
  939 + storage.push({
  940 + type: "function",
  941 + name: "save",
  942 + 'arguments': arguments
  943 + });
  944 + },
  945 + restore: function() {
  946 + storage.push({
  947 + type: "function",
  948 + name: "restore",
  949 + 'arguments': arguments
  950 + });
  951 + },
  952 + fillRect: function () {
  953 + storage.push({
  954 + type: "function",
  955 + name: "fillRect",
  956 + 'arguments': arguments
  957 + });
  958 + },
  959 + createPattern: function() {
  960 + storage.push({
  961 + type: "function",
  962 + name: "createPattern",
  963 + 'arguments': arguments
  964 + });
  965 + },
  966 + drawShape: function() {
  967 +
  968 + var shape = [];
  969 +
  970 + storage.push({
  971 + type: "function",
  972 + name: "drawShape",
  973 + 'arguments': shape
  974 + });
  975 +
  976 + return {
  977 + moveTo: function() {
  978 + shape.push({
  979 + name: "moveTo",
  980 + 'arguments': arguments
  981 + });
  982 + },
  983 + lineTo: function() {
  984 + shape.push({
  985 + name: "lineTo",
  986 + 'arguments': arguments
  987 + });
  988 + },
  989 + arcTo: function() {
  990 + shape.push({
  991 + name: "arcTo",
  992 + 'arguments': arguments
  993 + });
  994 + },
  995 + bezierCurveTo: function() {
  996 + shape.push({
  997 + name: "bezierCurveTo",
  998 + 'arguments': arguments
  999 + });
  1000 + },
  1001 + quadraticCurveTo: function() {
  1002 + shape.push({
  1003 + name: "quadraticCurveTo",
  1004 + 'arguments': arguments
  1005 + });
  1006 + }
  1007 + };
  1008 +
  1009 + },
  1010 + drawImage: function () {
  1011 + storage.push({
  1012 + type: "function",
  1013 + name: "drawImage",
  1014 + 'arguments': arguments
  1015 + });
  1016 + },
  1017 + fillText: function () {
  1018 + storage.push({
  1019 + type: "function",
  1020 + name: "fillText",
  1021 + 'arguments': arguments
  1022 + });
  1023 + },
  1024 + setVariable: function (variable, value) {
  1025 + storage.push({
  1026 + type: "variable",
  1027 + name: variable,
  1028 + 'arguments': value
  1029 + });
  1030 + return value;
  1031 + }
  1032 + };
  1033 +}
  1034 +_html2canvas.Parse = function (images, options, cb) {
  1035 + window.scroll(0,0);
  1036 +
  1037 + var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
  1038 + numDraws = 0,
  1039 + doc = element.ownerDocument,
  1040 + Util = _html2canvas.Util,
  1041 + support = Util.Support(options, doc),
  1042 + ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
  1043 + body = doc.body,
  1044 + getCSS = Util.getCSS,
  1045 + pseudoHide = "___html2canvas___pseudoelement",
  1046 + hidePseudoElementsStyles = doc.createElement('style');
  1047 +
  1048 + hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
  1049 + '-parent:before { content: "" !important; display: none !important; }' +
  1050 + '.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
  1051 +
  1052 + body.appendChild(hidePseudoElementsStyles);
  1053 +
  1054 + images = images || {};
  1055 +
  1056 + init();
  1057 +
  1058 + function init() {
  1059 + var background = getCSS(document.documentElement, "backgroundColor"),
  1060 + transparentBackground = (Util.isTransparent(background) && element === document.body),
  1061 + stack = renderElement(element, null, false, transparentBackground);
  1062 +
  1063 + // create pseudo elements in a single pass to prevent synchronous layouts
  1064 + addPseudoElements(element);
  1065 +
  1066 + parseChildren(element, stack, function() {
  1067 + if (transparentBackground) {
  1068 + background = stack.backgroundColor;
  1069 + }
  1070 +
  1071 + removePseudoElements();
  1072 +
  1073 + Util.log('Done parsing, moving to Render.');
  1074 +
  1075 + cb({
  1076 + backgroundColor: background,
  1077 + stack: stack
  1078 + });
  1079 + });
  1080 + }
  1081 +
  1082 + // Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
  1083 + // so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
  1084 + // with layout.
  1085 + function addPseudoElements(el) {
  1086 + // These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
  1087 + // layouts & getPseudoElement calling getComputedStyle.
  1088 + var jobs = [], classes = [];
  1089 + getPseudoElementClasses();
  1090 + findPseudoElements(el);
  1091 + runJobs();
  1092 +
  1093 + function getPseudoElementClasses(){
  1094 + var findPsuedoEls = /:before|:after/;
  1095 + var sheets = document.styleSheets;
  1096 + for (var i = 0, j = sheets.length; i < j; i++) {
  1097 + try {
  1098 + var rules = sheets[i].cssRules;
  1099 + for (var k = 0, l = rules.length; k < l; k++) {
  1100 + if(findPsuedoEls.test(rules[k].selectorText)) {
  1101 + classes.push(rules[k].selectorText);
  1102 + }
  1103 + }
  1104 + }
  1105 + catch(e) { // will throw security exception for style sheets loaded from external domains
  1106 + }
  1107 + }
  1108 +
  1109 + // Trim off the :after and :before (or ::after and ::before)
  1110 + for (i = 0, j = classes.length; i < j; i++) {
  1111 + classes[i] = classes[i].match(/(^[^:]*)/)[1];
  1112 + }
  1113 + }
  1114 +
  1115 + // Using the list of elements we know how pseudo el styles, create fake pseudo elements.
  1116 + function findPseudoElements(el) {
  1117 + var els = document.querySelectorAll(classes.join(','));
  1118 + for(var i = 0, j = els.length; i < j; i++) {
  1119 + createPseudoElements(els[i]);
  1120 + }
  1121 + }
  1122 +
  1123 + // Create pseudo elements & add them to a job queue.
  1124 + function createPseudoElements(el) {
  1125 + var before = getPseudoElement(el, ':before'),
  1126 + after = getPseudoElement(el, ':after');
  1127 +
  1128 + if(before) {
  1129 + jobs.push({type: 'before', pseudo: before, el: el});
  1130 + }
  1131 +
  1132 + if (after) {
  1133 + jobs.push({type: 'after', pseudo: after, el: el});
  1134 + }
  1135 + }
  1136 +
  1137 + // Adds a class to the pseudo's parent to prevent the original before/after from messing
  1138 + // with layouts.
  1139 + // Execute the inserts & addClass() calls in a batch to prevent relayouts.
  1140 + function runJobs() {
  1141 + // Add Class
  1142 + jobs.forEach(function(job){
  1143 + addClass(job.el, pseudoHide + "-parent");
  1144 + });
  1145 +
  1146 + // Insert el
  1147 + jobs.forEach(function(job){
  1148 + if(job.type === 'before'){
  1149 + job.el.insertBefore(job.pseudo, job.el.firstChild);
  1150 + } else {
  1151 + job.el.appendChild(job.pseudo);
  1152 + }
  1153 + });
  1154 + }
  1155 + }
  1156 +
  1157 +
  1158 +
  1159 + // Delete our fake pseudo elements from the DOM. This will remove those actual elements
  1160 + // and the classes on their parents that hide the actual pseudo elements.
  1161 + // Note that NodeLists are 'live' collections so you can't use a for loop here. They are
  1162 + // actually deleted from the NodeList after each iteration.
  1163 + function removePseudoElements(){
  1164 + // delete pseudo elements
  1165 + body.removeChild(hidePseudoElementsStyles);
  1166 + var pseudos = document.getElementsByClassName(pseudoHide + "-element");
  1167 + while (pseudos.length) {
  1168 + pseudos[0].parentNode.removeChild(pseudos[0]);
  1169 + }
  1170 +
  1171 + // Remove pseudo hiding classes
  1172 + var parents = document.getElementsByClassName(pseudoHide + "-parent");
  1173 + while(parents.length) {
  1174 + removeClass(parents[0], pseudoHide + "-parent");
  1175 + }
  1176 + }
  1177 +
  1178 + function addClass (el, className) {
  1179 + if (el.classList) {
  1180 + el.classList.add(className);
  1181 + } else {
  1182 + el.className = el.className + " " + className;
  1183 + }
  1184 + }
  1185 +
  1186 + function removeClass (el, className) {
  1187 + if (el.classList) {
  1188 + el.classList.remove(className);
  1189 + } else {
  1190 + el.className = el.className.replace(className, "").trim();
  1191 + }
  1192 + }
  1193 +
  1194 + function hasClass (el, className) {
  1195 + return el.className.indexOf(className) > -1;
  1196 + }
  1197 +
  1198 + // Note that this doesn't work in < IE8, but we don't support that anyhow
  1199 + function nodeListToArray (nodeList) {
  1200 + return Array.prototype.slice.call(nodeList);
  1201 + }
  1202 +
  1203 + function documentWidth () {
  1204 + return Math.max(
  1205 + Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
  1206 + Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
  1207 + Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
  1208 + );
  1209 + }
  1210 +
  1211 + function documentHeight () {
  1212 + return Math.max(
  1213 + Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
  1214 + Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
  1215 + Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
  1216 + );
  1217 + }
  1218 +
  1219 + function getCSSInt(element, attribute) {
  1220 + var val = parseInt(getCSS(element, attribute), 10);
  1221 + return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html
  1222 + }
  1223 +
  1224 + function renderRect (ctx, x, y, w, h, bgcolor) {
  1225 + if (bgcolor !== "transparent"){
  1226 + ctx.setVariable("fillStyle", bgcolor);
  1227 + ctx.fillRect(x, y, w, h);
  1228 + numDraws+=1;
  1229 + }
  1230 + }
  1231 +
  1232 + function capitalize(m, p1, p2) {
  1233 + if (m.length > 0) {
  1234 + return p1 + p2.toUpperCase();
  1235 + }
  1236 + }
  1237 +
  1238 + function textTransform (text, transform) {
  1239 + switch(transform){
  1240 + case "lowercase":
  1241 + return text.toLowerCase();
  1242 + case "capitalize":
  1243 + return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize);
  1244 + case "uppercase":
  1245 + return text.toUpperCase();
  1246 + default:
  1247 + return text;
  1248 + }
  1249 + }
  1250 +
  1251 + function noLetterSpacing(letter_spacing) {
  1252 + return (/^(normal|none|0px)$/.test(letter_spacing));
  1253 + }
  1254 +
  1255 + function drawText(currentText, x, y, ctx){
  1256 + if (currentText !== null && Util.trimText(currentText).length > 0) {
  1257 + ctx.fillText(currentText, x, y);
  1258 + numDraws+=1;
  1259 + }
  1260 + }
  1261 +
  1262 + function setTextVariables(ctx, el, text_decoration, color) {
  1263 + var align = false,
  1264 + bold = getCSS(el, "fontWeight"),
  1265 + family = getCSS(el, "fontFamily"),
  1266 + size = getCSS(el, "fontSize"),
  1267 + shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
  1268 +
  1269 + switch(parseInt(bold, 10)){
  1270 + case 401:
  1271 + bold = "bold";
  1272 + break;
  1273 + case 400:
  1274 + bold = "normal";
  1275 + break;
  1276 + }
  1277 +
  1278 + ctx.setVariable("fillStyle", color);
  1279 + ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
  1280 + ctx.setVariable("textAlign", (align) ? "right" : "left");
  1281 +
  1282 + if (shadows.length) {
  1283 + // TODO: support multiple text shadows
  1284 + // apply the first text shadow
  1285 + ctx.setVariable("shadowColor", shadows[0].color);
  1286 + ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
  1287 + ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
  1288 + ctx.setVariable("shadowBlur", shadows[0].blur);
  1289 + }
  1290 +
  1291 + if (text_decoration !== "none"){
  1292 + return Util.Font(family, size, doc);
  1293 + }
  1294 + }
  1295 +
  1296 + function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) {
  1297 + switch(text_decoration) {
  1298 + case "underline":
  1299 + // Draws a line at the baseline of the font
  1300 + // 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
  1301 + renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color);
  1302 + break;
  1303 + case "overline":
  1304 + renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color);
  1305 + break;
  1306 + case "line-through":
  1307 + // TODO try and find exact position for line-through
  1308 + renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color);
  1309 + break;
  1310 + }
  1311 + }
  1312 +
  1313 + function getTextBounds(state, text, textDecoration, isLast, transform) {
  1314 + var bounds;
  1315 + if (support.rangeBounds && !transform) {
  1316 + if (textDecoration !== "none" || Util.trimText(text).length !== 0) {
  1317 + bounds = textRangeBounds(text, state.node, state.textOffset);
  1318 + }
  1319 + state.textOffset += text.length;
  1320 + } else if (state.node && typeof state.node.nodeValue === "string" ){
  1321 + var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
  1322 + bounds = textWrapperBounds(state.node, transform);
  1323 + state.node = newTextNode;
  1324 + }
  1325 + return bounds;
  1326 + }
  1327 +
  1328 + function textRangeBounds(text, textNode, textOffset) {
  1329 + var range = doc.createRange();
  1330 + range.setStart(textNode, textOffset);
  1331 + range.setEnd(textNode, textOffset + text.length);
  1332 + return range.getBoundingClientRect();
  1333 + }
  1334 +
  1335 + function textWrapperBounds(oldTextNode, transform) {
  1336 + var parent = oldTextNode.parentNode,
  1337 + wrapElement = doc.createElement('wrapper'),
  1338 + backupText = oldTextNode.cloneNode(true);
  1339 +
  1340 + wrapElement.appendChild(oldTextNode.cloneNode(true));
  1341 + parent.replaceChild(wrapElement, oldTextNode);
  1342 +
  1343 + var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
  1344 + parent.replaceChild(backupText, wrapElement);
  1345 + return bounds;
  1346 + }
  1347 +
  1348 + function renderText(el, textNode, stack) {
  1349 + var ctx = stack.ctx,
  1350 + color = getCSS(el, "color"),
  1351 + textDecoration = getCSS(el, "textDecoration"),
  1352 + textAlign = getCSS(el, "textAlign"),
  1353 + metrics,
  1354 + textList,
  1355 + state = {
  1356 + node: textNode,
  1357 + textOffset: 0
  1358 + };
  1359 +
  1360 + if (Util.trimText(textNode.nodeValue).length > 0) {
  1361 + textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
  1362 + textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
  1363 +
  1364 + textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
  1365 + textNode.nodeValue.split(/(\b| )/)
  1366 + : textNode.nodeValue.split("");
  1367 +
  1368 + metrics = setTextVariables(ctx, el, textDecoration, color);
  1369 +
  1370 + if (options.chinese) {
  1371 + textList.forEach(function(word, index) {
  1372 + if (/.*[\u4E00-\u9FA5].*$/.test(word)) {
  1373 + word = word.split("");
  1374 + word.unshift(index, 1);
  1375 + textList.splice.apply(textList, word);
  1376 + }
  1377 + });
  1378 + }
  1379 +
  1380 + textList.forEach(function(text, index) {
  1381 + var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
  1382 + if (bounds) {
  1383 + drawText(text, bounds.left, bounds.bottom, ctx);
  1384 + renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
  1385 + }
  1386 + });
  1387 + }
  1388 + }
  1389 +
  1390 + function listPosition (element, val) {
  1391 + var boundElement = doc.createElement( "boundelement" ),
  1392 + originalType,
  1393 + bounds;
  1394 +
  1395 + boundElement.style.display = "inline";
  1396 +
  1397 + originalType = element.style.listStyleType;
  1398 + element.style.listStyleType = "none";
  1399 +
  1400 + boundElement.appendChild(doc.createTextNode(val));
  1401 +
  1402 + element.insertBefore(boundElement, element.firstChild);
  1403 +
  1404 + bounds = Util.Bounds(boundElement);
  1405 + element.removeChild(boundElement);
  1406 + element.style.listStyleType = originalType;
  1407 + return bounds;
  1408 + }
  1409 +
  1410 + function elementIndex(el) {
  1411 + var i = -1,
  1412 + count = 1,
  1413 + childs = el.parentNode.childNodes;
  1414 +
  1415 + if (el.parentNode) {
  1416 + while(childs[++i] !== el) {
  1417 + if (childs[i].nodeType === 1) {
  1418 + count++;
  1419 + }
  1420 + }
  1421 + return count;
  1422 + } else {
  1423 + return -1;
  1424 + }
  1425 + }
  1426 +
  1427 + function listItemText(element, type) {
  1428 + var currentIndex = elementIndex(element), text;
  1429 + switch(type){
  1430 + case "decimal":
  1431 + text = currentIndex;
  1432 + break;
  1433 + case "decimal-leading-zero":
  1434 + text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString();
  1435 + break;
  1436 + case "upper-roman":
  1437 + text = _html2canvas.Generate.ListRoman( currentIndex );
  1438 + break;
  1439 + case "lower-roman":
  1440 + text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase();
  1441 + break;
  1442 + case "lower-alpha":
  1443 + text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase();
  1444 + break;
  1445 + case "upper-alpha":
  1446 + text = _html2canvas.Generate.ListAlpha( currentIndex );
  1447 + break;
  1448 + }
  1449 +
  1450 + return text + ". ";
  1451 + }
  1452 +
  1453 + function renderListItem(element, stack, elBounds) {
  1454 + var x,
  1455 + text,
  1456 + ctx = stack.ctx,
  1457 + type = getCSS(element, "listStyleType"),
  1458 + listBounds;
  1459 +
  1460 + if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) {
  1461 + text = listItemText(element, type);
  1462 + listBounds = listPosition(element, text);
  1463 + setTextVariables(ctx, element, "none", getCSS(element, "color"));
  1464 +
  1465 + if (getCSS(element, "listStylePosition") === "inside") {
  1466 + ctx.setVariable("textAlign", "left");
  1467 + x = elBounds.left;
  1468 + } else {
  1469 + return;
  1470 + }
  1471 +
  1472 + drawText(text, x, listBounds.bottom, ctx);
  1473 + }
  1474 + }
  1475 +
  1476 + function loadImage (src){
  1477 + var img = images[src];
  1478 + return (img && img.succeeded === true) ? img.img : false;
  1479 + }
  1480 +
  1481 + function clipBounds(src, dst){
  1482 + var x = Math.max(src.left, dst.left),
  1483 + y = Math.max(src.top, dst.top),
  1484 + x2 = Math.min((src.left + src.width), (dst.left + dst.width)),
  1485 + y2 = Math.min((src.top + src.height), (dst.top + dst.height));
  1486 +
  1487 + return {
  1488 + left:x,
  1489 + top:y,
  1490 + width:x2-x,
  1491 + height:y2-y
  1492 + };
  1493 + }
  1494 +
  1495 + function setZ(element, stack, parentStack){
  1496 + var newContext,
  1497 + isPositioned = stack.cssPosition !== 'static',
  1498 + zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
  1499 + opacity = getCSS(element, 'opacity'),
  1500 + isFloated = getCSS(element, 'cssFloat') !== 'none';
  1501 +
  1502 + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
  1503 + // When a new stacking context should be created:
  1504 + // the root element (HTML),
  1505 + // positioned (absolutely or relatively) with a z-index value other than "auto",
  1506 + // elements with an opacity value less than 1. (See the specification for opacity),
  1507 + // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
  1508 +
  1509 + stack.zIndex = newContext = h2czContext(zIndex);
  1510 + newContext.isPositioned = isPositioned;
  1511 + newContext.isFloated = isFloated;
  1512 + newContext.opacity = opacity;
  1513 + newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);
  1514 + newContext.depth = parentStack ? (parentStack.zIndex.depth + 1) : 0;
  1515 +
  1516 + if (parentStack) {
  1517 + parentStack.zIndex.children.push(stack);
  1518 + }
  1519 + }
  1520 +
  1521 + function h2czContext(zindex) {
  1522 + return {
  1523 + depth: 0,
  1524 + zindex: zindex,
  1525 + children: []
  1526 + };
  1527 + }
  1528 +
  1529 + function renderImage(ctx, element, image, bounds, borders) {
  1530 +
  1531 + var paddingLeft = getCSSInt(element, 'paddingLeft'),
  1532 + paddingTop = getCSSInt(element, 'paddingTop'),
  1533 + paddingRight = getCSSInt(element, 'paddingRight'),
  1534 + paddingBottom = getCSSInt(element, 'paddingBottom');
  1535 +
  1536 + drawImage(
  1537 + ctx,
  1538 + image,
  1539 + 0, //sx
  1540 + 0, //sy
  1541 + image.width, //sw
  1542 + image.height, //sh
  1543 + bounds.left + paddingLeft + borders[3].width, //dx
  1544 + bounds.top + paddingTop + borders[0].width, // dy
  1545 + bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw
  1546 + bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh
  1547 + );
  1548 + }
  1549 +
  1550 + function getBorderData(element) {
  1551 + return ["Top", "Right", "Bottom", "Left"].map(function(side) {
  1552 + return {
  1553 + width: getCSSInt(element, 'border' + side + 'Width'),
  1554 + color: getCSS(element, 'border' + side + 'Color')
  1555 + };
  1556 + });
  1557 + }
  1558 +
  1559 + function getBorderRadiusData(element) {
  1560 + return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
  1561 + return getCSS(element, 'border' + side + 'Radius');
  1562 + });
  1563 + }
  1564 +
  1565 + function getCurvePoints(x, y, r1, r2) {
  1566 + var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
  1567 + var ox = (r1) * kappa, // control point offset horizontal
  1568 + oy = (r2) * kappa, // control point offset vertical
  1569 + xm = x + r1, // x-middle
  1570 + ym = y + r2; // y-middle
  1571 + return {
  1572 + topLeft: bezierCurve({
  1573 + x:x,
  1574 + y:ym
  1575 + }, {
  1576 + x:x,
  1577 + y:ym - oy
  1578 + }, {
  1579 + x:xm - ox,
  1580 + y:y
  1581 + }, {
  1582 + x:xm,
  1583 + y:y
  1584 + }),
  1585 + topRight: bezierCurve({
  1586 + x:x,
  1587 + y:y
  1588 + }, {
  1589 + x:x + ox,
  1590 + y:y
  1591 + }, {
  1592 + x:xm,
  1593 + y:ym - oy
  1594 + }, {
  1595 + x:xm,
  1596 + y:ym
  1597 + }),
  1598 + bottomRight: bezierCurve({
  1599 + x:xm,
  1600 + y:y
  1601 + }, {
  1602 + x:xm,
  1603 + y:y + oy
  1604 + }, {
  1605 + x:x + ox,
  1606 + y:ym
  1607 + }, {
  1608 + x:x,
  1609 + y:ym
  1610 + }),
  1611 + bottomLeft: bezierCurve({
  1612 + x:xm,
  1613 + y:ym
  1614 + }, {
  1615 + x:xm - ox,
  1616 + y:ym
  1617 + }, {
  1618 + x:x,
  1619 + y:y + oy
  1620 + }, {
  1621 + x:x,
  1622 + y:y
  1623 + })
  1624 + };
  1625 + }
  1626 +
  1627 + function bezierCurve(start, startControl, endControl, end) {
  1628 +
  1629 + var lerp = function (a, b, t) {
  1630 + return {
  1631 + x:a.x + (b.x - a.x) * t,
  1632 + y:a.y + (b.y - a.y) * t
  1633 + };
  1634 + };
  1635 +
  1636 + return {
  1637 + start: start,
  1638 + startControl: startControl,
  1639 + endControl: endControl,
  1640 + end: end,
  1641 + subdivide: function(t) {
  1642 + var ab = lerp(start, startControl, t),
  1643 + bc = lerp(startControl, endControl, t),
  1644 + cd = lerp(endControl, end, t),
  1645 + abbc = lerp(ab, bc, t),
  1646 + bccd = lerp(bc, cd, t),
  1647 + dest = lerp(abbc, bccd, t);
  1648 + return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
  1649 + },
  1650 + curveTo: function(borderArgs) {
  1651 + borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
  1652 + },
  1653 + curveToReversed: function(borderArgs) {
  1654 + borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
  1655 + }
  1656 + };
  1657 + }
  1658 +
  1659 + function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
  1660 + if (radius1[0] > 0 || radius1[1] > 0) {
  1661 + borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
  1662 + corner1[0].curveTo(borderArgs);
  1663 + corner1[1].curveTo(borderArgs);
  1664 + } else {
  1665 + borderArgs.push(["line", x, y]);
  1666 + }
  1667 +
  1668 + if (radius2[0] > 0 || radius2[1] > 0) {
  1669 + borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
  1670 + }
  1671 + }
  1672 +
  1673 + function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
  1674 + var borderArgs = [];
  1675 +
  1676 + if (radius1[0] > 0 || radius1[1] > 0) {
  1677 + borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
  1678 + outer1[1].curveTo(borderArgs);
  1679 + } else {
  1680 + borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
  1681 + }
  1682 +
  1683 + if (radius2[0] > 0 || radius2[1] > 0) {
  1684 + borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
  1685 + outer2[0].curveTo(borderArgs);
  1686 + borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
  1687 + inner2[0].curveToReversed(borderArgs);
  1688 + } else {
  1689 + borderArgs.push([ "line", borderData.c2[0], borderData.c2[1]]);
  1690 + borderArgs.push([ "line", borderData.c3[0], borderData.c3[1]]);
  1691 + }
  1692 +
  1693 + if (radius1[0] > 0 || radius1[1] > 0) {
  1694 + borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
  1695 + inner1[1].curveToReversed(borderArgs);
  1696 + } else {
  1697 + borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]);
  1698 + }
  1699 +
  1700 + return borderArgs;
  1701 + }
  1702 +
  1703 + function calculateCurvePoints(bounds, borderRadius, borders) {
  1704 +
  1705 + var x = bounds.left,
  1706 + y = bounds.top,
  1707 + width = bounds.width,
  1708 + height = bounds.height,
  1709 +
  1710 + tlh = borderRadius[0][0],
  1711 + tlv = borderRadius[0][1],
  1712 + trh = borderRadius[1][0],
  1713 + trv = borderRadius[1][1],
  1714 + brh = borderRadius[2][0],
  1715 + brv = borderRadius[2][1],
  1716 + blh = borderRadius[3][0],
  1717 + blv = borderRadius[3][1],
  1718 +
  1719 + topWidth = width - trh,
  1720 + rightHeight = height - brv,
  1721 + bottomWidth = width - brh,
  1722 + leftHeight = height - blv;
  1723 +
  1724 + return {
  1725 + topLeftOuter: getCurvePoints(
  1726 + x,
  1727 + y,
  1728 + tlh,
  1729 + tlv
  1730 + ).topLeft.subdivide(0.5),
  1731 +
  1732 + topLeftInner: getCurvePoints(
  1733 + x + borders[3].width,
  1734 + y + borders[0].width,
  1735 + Math.max(0, tlh - borders[3].width),
  1736 + Math.max(0, tlv - borders[0].width)
  1737 + ).topLeft.subdivide(0.5),
  1738 +
  1739 + topRightOuter: getCurvePoints(
  1740 + x + topWidth,
  1741 + y,
  1742 + trh,
  1743 + trv
  1744 + ).topRight.subdivide(0.5),
  1745 +
  1746 + topRightInner: getCurvePoints(
  1747 + x + Math.min(topWidth, width + borders[3].width),
  1748 + y + borders[0].width,
  1749 + (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
  1750 + trv - borders[0].width
  1751 + ).topRight.subdivide(0.5),
  1752 +
  1753 + bottomRightOuter: getCurvePoints(
  1754 + x + bottomWidth,
  1755 + y + rightHeight,
  1756 + brh,
  1757 + brv
  1758 + ).bottomRight.subdivide(0.5),
  1759 +
  1760 + bottomRightInner: getCurvePoints(
  1761 + x + Math.min(bottomWidth, width + borders[3].width),
  1762 + y + Math.min(rightHeight, height + borders[0].width),
  1763 + Math.max(0, brh - borders[1].width),
  1764 + Math.max(0, brv - borders[2].width)
  1765 + ).bottomRight.subdivide(0.5),
  1766 +
  1767 + bottomLeftOuter: getCurvePoints(
  1768 + x,
  1769 + y + leftHeight,
  1770 + blh,
  1771 + blv
  1772 + ).bottomLeft.subdivide(0.5),
  1773 +
  1774 + bottomLeftInner: getCurvePoints(
  1775 + x + borders[3].width,
  1776 + y + leftHeight,
  1777 + Math.max(0, blh - borders[3].width),
  1778 + Math.max(0, blv - borders[2].width)
  1779 + ).bottomLeft.subdivide(0.5)
  1780 + };
  1781 + }
  1782 +
  1783 + function getBorderClip(element, borderPoints, borders, radius, bounds) {
  1784 + var backgroundClip = getCSS(element, 'backgroundClip'),
  1785 + borderArgs = [];
  1786 +
  1787 + switch(backgroundClip) {
  1788 + case "content-box":
  1789 + case "padding-box":
  1790 + parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
  1791 + parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
  1792 + parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
  1793 + parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
  1794 + break;
  1795 +
  1796 + default:
  1797 + parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
  1798 + parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
  1799 + parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
  1800 + parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
  1801 + break;
  1802 + }
  1803 +
  1804 + return borderArgs;
  1805 + }
  1806 +
  1807 + function parseBorders(element, bounds, borders){
  1808 + var x = bounds.left,
  1809 + y = bounds.top,
  1810 + width = bounds.width,
  1811 + height = bounds.height,
  1812 + borderSide,
  1813 + bx,
  1814 + by,
  1815 + bw,
  1816 + bh,
  1817 + borderArgs,
  1818 + // http://www.w3.org/TR/css3-background/#the-border-radius
  1819 + borderRadius = getBorderRadiusData(element),
  1820 + borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
  1821 + borderData = {
  1822 + clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
  1823 + borders: []
  1824 + };
  1825 +
  1826 + for (borderSide = 0; borderSide < 4; borderSide++) {
  1827 +
  1828 + if (borders[borderSide].width > 0) {
  1829 + bx = x;
  1830 + by = y;
  1831 + bw = width;
  1832 + bh = height - (borders[2].width);
  1833 +
  1834 + switch(borderSide) {
  1835 + case 0:
  1836 + // top border
  1837 + bh = borders[0].width;
  1838 +
  1839 + borderArgs = drawSide({
  1840 + c1: [bx, by],
  1841 + c2: [bx + bw, by],
  1842 + c3: [bx + bw - borders[1].width, by + bh],
  1843 + c4: [bx + borders[3].width, by + bh]
  1844 + }, borderRadius[0], borderRadius[1],
  1845 + borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
  1846 + break;
  1847 + case 1:
  1848 + // right border
  1849 + bx = x + width - (borders[1].width);
  1850 + bw = borders[1].width;
  1851 +
  1852 + borderArgs = drawSide({
  1853 + c1: [bx + bw, by],
  1854 + c2: [bx + bw, by + bh + borders[2].width],
  1855 + c3: [bx, by + bh],
  1856 + c4: [bx, by + borders[0].width]
  1857 + }, borderRadius[1], borderRadius[2],
  1858 + borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
  1859 + break;
  1860 + case 2:
  1861 + // bottom border
  1862 + by = (by + height) - (borders[2].width);
  1863 + bh = borders[2].width;
  1864 +
  1865 + borderArgs = drawSide({
  1866 + c1: [bx + bw, by + bh],
  1867 + c2: [bx, by + bh],
  1868 + c3: [bx + borders[3].width, by],
  1869 + c4: [bx + bw - borders[3].width, by]
  1870 + }, borderRadius[2], borderRadius[3],
  1871 + borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
  1872 + break;
  1873 + case 3:
  1874 + // left border
  1875 + bw = borders[3].width;
  1876 +
  1877 + borderArgs = drawSide({
  1878 + c1: [bx, by + bh + borders[2].width],
  1879 + c2: [bx, by],
  1880 + c3: [bx + bw, by + borders[0].width],
  1881 + c4: [bx + bw, by + bh]
  1882 + }, borderRadius[3], borderRadius[0],
  1883 + borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
  1884 + break;
  1885 + }
  1886 +
  1887 + borderData.borders.push({
  1888 + args: borderArgs,
  1889 + color: borders[borderSide].color
  1890 + });
  1891 +
  1892 + }
  1893 + }
  1894 +
  1895 + return borderData;
  1896 + }
  1897 +
  1898 + function createShape(ctx, args) {
  1899 + var shape = ctx.drawShape();
  1900 + args.forEach(function(border, index) {
  1901 + shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
  1902 + });
  1903 + return shape;
  1904 + }
  1905 +
  1906 + function renderBorders(ctx, borderArgs, color) {
  1907 + if (color !== "transparent") {
  1908 + ctx.setVariable( "fillStyle", color);
  1909 + createShape(ctx, borderArgs);
  1910 + ctx.fill();
  1911 + numDraws+=1;
  1912 + }
  1913 + }
  1914 +
  1915 + function renderFormValue (el, bounds, stack){
  1916 +
  1917 + var valueWrap = doc.createElement('valuewrap'),
  1918 + cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'],
  1919 + textValue,
  1920 + textNode;
  1921 +
  1922 + cssPropertyArray.forEach(function(property) {
  1923 + try {
  1924 + valueWrap.style[property] = getCSS(el, property);
  1925 + } catch(e) {
  1926 + // Older IE has issues with "border"
  1927 + Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
  1928 + }
  1929 + });
  1930 +
  1931 + valueWrap.style.borderColor = "black";
  1932 + valueWrap.style.borderStyle = "solid";
  1933 + valueWrap.style.display = "block";
  1934 + valueWrap.style.position = "absolute";
  1935 +
  1936 + if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){
  1937 + valueWrap.style.lineHeight = getCSS(el, "height");
  1938 + }
  1939 +
  1940 + valueWrap.style.top = bounds.top + "px";
  1941 + valueWrap.style.left = bounds.left + "px";
  1942 +
  1943 + textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value;
  1944 + if(!textValue) {
  1945 + textValue = el.placeholder;
  1946 + }
  1947 +
  1948 + textNode = doc.createTextNode(textValue);
  1949 +
  1950 + valueWrap.appendChild(textNode);
  1951 + body.appendChild(valueWrap);
  1952 +
  1953 + renderText(el, textNode, stack);
  1954 + body.removeChild(valueWrap);
  1955 + }
  1956 +
  1957 + function drawImage (ctx) {
  1958 + ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
  1959 + numDraws+=1;
  1960 + }
  1961 +
  1962 + function getPseudoElement(el, which) {
  1963 + var elStyle = window.getComputedStyle(el, which);
  1964 + var parentStyle = window.getComputedStyle(el);
  1965 + // If no content attribute is present, the pseudo element is hidden,
  1966 + // or the parent has a content property equal to the content on the pseudo element,
  1967 + // move along.
  1968 + if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
  1969 + elStyle.display === "none" || parentStyle.content === elStyle.content) {
  1970 + return;
  1971 + }
  1972 + var content = elStyle.content + '';
  1973 +
  1974 + // Strip inner quotes
  1975 + if(content[0] === "'" || content[0] === "\"") {
  1976 + content = content.replace(/(^['"])|(['"]$)/g, '');
  1977 + }
  1978 +
  1979 + var isImage = content.substr( 0, 3 ) === 'url',
  1980 + elps = document.createElement( isImage ? 'img' : 'span' );
  1981 +
  1982 + elps.className = pseudoHide + "-element ";
  1983 +
  1984 + Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
  1985 + // Prevent assigning of read only CSS Rules, ex. length, parentRule
  1986 + try {
  1987 + elps.style[prop] = elStyle[prop];
  1988 + } catch (e) {
  1989 + Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
  1990 + }
  1991 + });
  1992 +
  1993 + if(isImage) {
  1994 + elps.src = Util.parseBackgroundImage(content)[0].args[0];
  1995 + } else {
  1996 + elps.innerHTML = content;
  1997 + }
  1998 + return elps;
  1999 + }
  2000 +
  2001 + function indexedProperty(property) {
  2002 + return (isNaN(window.parseInt(property, 10)));
  2003 + }
  2004 +
  2005 + function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
  2006 + var offsetX = Math.round(bounds.left + backgroundPosition.left),
  2007 + offsetY = Math.round(bounds.top + backgroundPosition.top);
  2008 +
  2009 + ctx.createPattern(image);
  2010 + ctx.translate(offsetX, offsetY);
  2011 + ctx.fill();
  2012 + ctx.translate(-offsetX, -offsetY);
  2013 + }
  2014 +
  2015 + function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
  2016 + var args = [];
  2017 + args.push(["line", Math.round(left), Math.round(top)]);
  2018 + args.push(["line", Math.round(left + width), Math.round(top)]);
  2019 + args.push(["line", Math.round(left + width), Math.round(height + top)]);
  2020 + args.push(["line", Math.round(left), Math.round(height + top)]);
  2021 + createShape(ctx, args);
  2022 + ctx.save();
  2023 + ctx.clip();
  2024 + renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
  2025 + ctx.restore();
  2026 + }
  2027 +
  2028 + function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
  2029 + renderRect(
  2030 + ctx,
  2031 + backgroundBounds.left,
  2032 + backgroundBounds.top,
  2033 + backgroundBounds.width,
  2034 + backgroundBounds.height,
  2035 + bgcolor
  2036 + );
  2037 + }
  2038 +
  2039 + function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
  2040 + var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
  2041 + backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
  2042 + backgroundRepeat = Util.BackgroundRepeat(el, imageIndex);
  2043 +
  2044 + image = resizeImage(image, backgroundSize);
  2045 +
  2046 + switch (backgroundRepeat) {
  2047 + case "repeat-x":
  2048 + case "repeat no-repeat":
  2049 + backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
  2050 + bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
  2051 + break;
  2052 + case "repeat-y":
  2053 + case "no-repeat repeat":
  2054 + backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
  2055 + bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
  2056 + break;
  2057 + case "no-repeat":
  2058 + backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
  2059 + bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
  2060 + break;
  2061 + default:
  2062 + renderBackgroundRepeat(ctx, image, backgroundPosition, {
  2063 + top: bounds.top,
  2064 + left: bounds.left,
  2065 + width: image.width,
  2066 + height: image.height
  2067 + });
  2068 + break;
  2069 + }
  2070 + }
  2071 +
  2072 + function renderBackgroundImage(element, bounds, ctx) {
  2073 + var backgroundImage = getCSS(element, "backgroundImage"),
  2074 + backgroundImages = Util.parseBackgroundImage(backgroundImage),
  2075 + image,
  2076 + imageIndex = backgroundImages.length;
  2077 +
  2078 + while(imageIndex--) {
  2079 + backgroundImage = backgroundImages[imageIndex];
  2080 +
  2081 + if (!backgroundImage.args || backgroundImage.args.length === 0) {
  2082 + continue;
  2083 + }
  2084 +
  2085 + var key = backgroundImage.method === 'url' ?
  2086 + backgroundImage.args[0] :
  2087 + backgroundImage.value;
  2088 +
  2089 + image = loadImage(key);
  2090 +
  2091 + // TODO add support for background-origin
  2092 + if (image) {
  2093 + renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
  2094 + } else {
  2095 + Util.log("html2canvas: Error loading background:", backgroundImage);
  2096 + }
  2097 + }
  2098 + }
  2099 +
  2100 + function resizeImage(image, bounds) {
  2101 + if(image.width === bounds.width && image.height === bounds.height) {
  2102 + return image;
  2103 + }
  2104 +
  2105 + var ctx, canvas = doc.createElement('canvas');
  2106 + canvas.width = bounds.width;
  2107 + canvas.height = bounds.height;
  2108 + ctx = canvas.getContext("2d");
  2109 + drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height );
  2110 + return canvas;
  2111 + }
  2112 +
  2113 + function setOpacity(ctx, element, parentStack) {
  2114 + return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
  2115 + }
  2116 +
  2117 + function removePx(str) {
  2118 + return str.replace("px", "");
  2119 + }
  2120 +
  2121 + function getTransform(element, parentStack) {
  2122 + var transformRegExp = /(matrix)\((.+)\)/;
  2123 + var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
  2124 + 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";
  2125 +
  2126 + transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);
  2127 +
  2128 + var matrix;
  2129 + if (transform && transform !== "none") {
  2130 + var match = transform.match(transformRegExp);
  2131 + if (match) {
  2132 + switch(match[1]) {
  2133 + case "matrix":
  2134 + matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
  2135 + break;
  2136 + }
  2137 + }
  2138 + }
  2139 +
  2140 + return {
  2141 + origin: transformOrigin,
  2142 + matrix: matrix
  2143 + };
  2144 + }
  2145 +
  2146 + function createStack(element, parentStack, bounds, transform) {
  2147 + var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
  2148 + stack = {
  2149 + ctx: ctx,
  2150 + opacity: setOpacity(ctx, element, parentStack),
  2151 + cssPosition: getCSS(element, "position"),
  2152 + borders: getBorderData(element),
  2153 + transform: transform,
  2154 + clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null
  2155 + };
  2156 +
  2157 + setZ(element, stack, parentStack);
  2158 +
  2159 + // TODO correct overflow for absolute content residing under a static position
  2160 + if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
  2161 + stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
  2162 + }
  2163 +
  2164 + return stack;
  2165 + }
  2166 +
  2167 + function getBackgroundBounds(borders, bounds, clip) {
  2168 + var backgroundBounds = {
  2169 + left: bounds.left + borders[3].width,
  2170 + top: bounds.top + borders[0].width,
  2171 + width: bounds.width - (borders[1].width + borders[3].width),
  2172 + height: bounds.height - (borders[0].width + borders[2].width)
  2173 + };
  2174 +
  2175 + if (clip) {
  2176 + backgroundBounds = clipBounds(backgroundBounds, clip);
  2177 + }
  2178 +
  2179 + return backgroundBounds;
  2180 + }
  2181 +
  2182 + function getBounds(element, transform) {
  2183 + var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
  2184 + transform.origin[0] += bounds.left;
  2185 + transform.origin[1] += bounds.top;
  2186 + return bounds;
  2187 + }
  2188 +
  2189 + function renderElement(element, parentStack, ignoreBackground) {
  2190 + var transform = getTransform(element, parentStack),
  2191 + bounds = getBounds(element, transform),
  2192 + image,
  2193 + stack = createStack(element, parentStack, bounds, transform),
  2194 + borders = stack.borders,
  2195 + ctx = stack.ctx,
  2196 + backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
  2197 + borderData = parseBorders(element, bounds, borders),
  2198 + backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");
  2199 +
  2200 +
  2201 + createShape(ctx, borderData.clip);
  2202 +
  2203 + ctx.save();
  2204 + ctx.clip();
  2205 +
  2206 + if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
  2207 + renderBackgroundColor(ctx, bounds, backgroundColor);
  2208 + renderBackgroundImage(element, backgroundBounds, ctx);
  2209 + } else if (ignoreBackground) {
  2210 + stack.backgroundColor = backgroundColor;
  2211 + }
  2212 +
  2213 + ctx.restore();
  2214 +
  2215 + borderData.borders.forEach(function(border) {
  2216 + renderBorders(ctx, border.args, border.color);
  2217 + });
  2218 +
  2219 + switch(element.nodeName){
  2220 + case "IMG":
  2221 + if ((image = loadImage(element.getAttribute('src')))) {
  2222 + renderImage(ctx, element, image, bounds, borders);
  2223 + } else {
  2224 + Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
  2225 + }
  2226 + break;
  2227 + case "INPUT":
  2228 + // TODO add all relevant type's, i.e. HTML5 new stuff
  2229 + // todo add support for placeholder attribute for browsers which support it
  2230 + if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){
  2231 + renderFormValue(element, bounds, stack);
  2232 + }
  2233 + break;
  2234 + case "TEXTAREA":
  2235 + if ((element.value || element.placeholder || "").length > 0){
  2236 + renderFormValue(element, bounds, stack);
  2237 + }
  2238 + break;
  2239 + case "SELECT":
  2240 + if ((element.options||element.placeholder || "").length > 0){
  2241 + renderFormValue(element, bounds, stack);
  2242 + }
  2243 + break;
  2244 + case "LI":
  2245 + renderListItem(element, stack, backgroundBounds);
  2246 + break;
  2247 + case "CANVAS":
  2248 + renderImage(ctx, element, element, bounds, borders);
  2249 + break;
  2250 + }
  2251 +
  2252 + return stack;
  2253 + }
  2254 +
  2255 + function isElementVisible(element) {
  2256 + return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
  2257 + }
  2258 +
  2259 + function parseElement (element, stack, cb) {
  2260 + if (!cb) {
  2261 + cb = function(){};
  2262 + }
  2263 + if (isElementVisible(element)) {
  2264 + stack = renderElement(element, stack, false) || stack;
  2265 + if (!ignoreElementsRegExp.test(element.nodeName)) {
  2266 + return parseChildren(element, stack, cb);
  2267 + }
  2268 + }
  2269 + cb();
  2270 + }
  2271 +
  2272 + function parseChildren(element, stack, cb) {
  2273 + var children = Util.Children(element);
  2274 + // After all nodes have processed, finished() will call the cb.
  2275 + // We add one and kick it off so this will still work when children.length === 0.
  2276 + // Note that unless async is true, this will happen synchronously, just will callbacks.
  2277 + var jobs = children.length + 1;
  2278 + finished();
  2279 +
  2280 + if (options.async) {
  2281 + children.forEach(function(node) {
  2282 + // Don't block the page from rendering
  2283 + setTimeout(function(){ parseNode(node); }, 0);
  2284 + });
  2285 + } else {
  2286 + children.forEach(parseNode);
  2287 + }
  2288 +
  2289 + function parseNode(node) {
  2290 + if (node.nodeType === node.ELEMENT_NODE) {
  2291 + parseElement(node, stack, finished);
  2292 + } else if (node.nodeType === node.TEXT_NODE) {
  2293 + renderText(element, node, stack);
  2294 + finished();
  2295 + } else {
  2296 + finished();
  2297 + }
  2298 + }
  2299 + function finished(el) {
  2300 + if (--jobs <= 0){
  2301 + Util.log("finished rendering " + children.length + " children.");
  2302 + cb();
  2303 + }
  2304 + }
  2305 + }
  2306 +};
  2307 +_html2canvas.Preload = function( options ) {
  2308 +
  2309 + var images = {
  2310 + numLoaded: 0, // also failed are counted here
  2311 + numFailed: 0,
  2312 + numTotal: 0,
  2313 + cleanupDone: false
  2314 + },
  2315 + pageOrigin,
  2316 + Util = _html2canvas.Util,
  2317 + methods,
  2318 + i,
  2319 + count = 0,
  2320 + element = options.elements[0] || document.body,
  2321 + doc = element.ownerDocument,
  2322 + domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
  2323 + imgLen = domImages.length,
  2324 + link = doc.createElement("a"),
  2325 + supportCORS = (function( img ){
  2326 + return (img.crossOrigin !== undefined);
  2327 + })(new Image()),
  2328 + timeoutTimer;
  2329 +
  2330 + link.href = window.location.href;
  2331 + pageOrigin = link.protocol + link.host;
  2332 +
  2333 + function isSameOrigin(url){
  2334 + link.href = url;
  2335 + link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
  2336 + var origin = link.protocol + link.host;
  2337 + return (origin === pageOrigin);
  2338 + }
  2339 +
  2340 + function start(){
  2341 + Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
  2342 + if (!images.firstRun && images.numLoaded >= images.numTotal){
  2343 + Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
  2344 +
  2345 + if (typeof options.complete === "function"){
  2346 + options.complete(images);
  2347 + }
  2348 +
  2349 + }
  2350 + }
  2351 +
  2352 + // TODO modify proxy to serve images with CORS enabled, where available
  2353 + function proxyGetImage(url, img, imageObj){
  2354 + var callback_name,
  2355 + scriptUrl = options.proxy,
  2356 + script;
  2357 +
  2358 + link.href = url;
  2359 + url = link.href; // work around for pages with base href="" set - WARNING: this may change the url
  2360 +
  2361 + callback_name = 'html2canvas_' + (count++);
  2362 + imageObj.callbackname = callback_name;
  2363 +
  2364 + if (scriptUrl.indexOf("?") > -1) {
  2365 + scriptUrl += "&";
  2366 + } else {
  2367 + scriptUrl += "?";
  2368 + }
  2369 + scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
  2370 + script = doc.createElement("script");
  2371 +
  2372 + window[callback_name] = function(a){
  2373 + if (a.substring(0,6) === "error:"){
  2374 + imageObj.succeeded = false;
  2375 + images.numLoaded++;
  2376 + images.numFailed++;
  2377 + start();
  2378 + } else {
  2379 + setImageLoadHandlers(img, imageObj);
  2380 + img.src = a;
  2381 + }
  2382 + 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)
  2383 + try {
  2384 + delete window[callback_name]; // for all browser that support this
  2385 + } catch(ex) {}
  2386 + script.parentNode.removeChild(script);
  2387 + script = null;
  2388 + delete imageObj.script;
  2389 + delete imageObj.callbackname;
  2390 + };
  2391 +
  2392 + script.setAttribute("type", "text/javascript");
  2393 + script.setAttribute("src", scriptUrl);
  2394 + imageObj.script = script;
  2395 + window.document.body.appendChild(script);
  2396 +
  2397 + }
  2398 +
  2399 + function loadPseudoElement(element, type) {
  2400 + var style = window.getComputedStyle(element, type),
  2401 + content = style.content;
  2402 + if (content.substr(0, 3) === 'url') {
  2403 + methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
  2404 + }
  2405 + loadBackgroundImages(style.backgroundImage, element);
  2406 + }
  2407 +
  2408 + function loadPseudoElementImages(element) {
  2409 + loadPseudoElement(element, ":before");
  2410 + loadPseudoElement(element, ":after");
  2411 + }
  2412 +
  2413 + function loadGradientImage(backgroundImage, bounds) {
  2414 + var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);
  2415 +
  2416 + if (img !== undefined){
  2417 + images[backgroundImage] = {
  2418 + img: img,
  2419 + succeeded: true
  2420 + };
  2421 + images.numTotal++;
  2422 + images.numLoaded++;
  2423 + start();
  2424 + }
  2425 + }
  2426 +
  2427 + function invalidBackgrounds(background_image) {
  2428 + return (background_image && background_image.method && background_image.args && background_image.args.length > 0 );
  2429 + }
  2430 +
  2431 + function loadBackgroundImages(background_image, el) {
  2432 + var bounds;
  2433 +
  2434 + _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
  2435 + if (background_image.method === 'url') {
  2436 + methods.loadImage(background_image.args[0]);
  2437 + } else if(background_image.method.match(/\-?gradient$/)) {
  2438 + if(bounds === undefined) {
  2439 + bounds = _html2canvas.Util.Bounds(el);
  2440 + }
  2441 + loadGradientImage(background_image.value, bounds);
  2442 + }
  2443 + });
  2444 + }
  2445 +
  2446 + function getImages (el) {
  2447 + var elNodeType = false;
  2448 +
  2449 + // Firefox fails with permission denied on pages with iframes
  2450 + try {
  2451 + Util.Children(el).forEach(getImages);
  2452 + }
  2453 + catch( e ) {}
  2454 +
  2455 + try {
  2456 + elNodeType = el.nodeType;
  2457 + } catch (ex) {
  2458 + elNodeType = false;
  2459 + Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
  2460 + }
  2461 +
  2462 + if (elNodeType === 1 || elNodeType === undefined) {
  2463 + loadPseudoElementImages(el);
  2464 + try {
  2465 + loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
  2466 + } catch(e) {
  2467 + Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
  2468 + }
  2469 + loadBackgroundImages(el);
  2470 + }
  2471 + }
  2472 +
  2473 + function setImageLoadHandlers(img, imageObj) {
  2474 + img.onload = function() {
  2475 + if ( imageObj.timer !== undefined ) {
  2476 + // CORS succeeded
  2477 + window.clearTimeout( imageObj.timer );
  2478 + }
  2479 +
  2480 + images.numLoaded++;
  2481 + imageObj.succeeded = true;
  2482 + img.onerror = img.onload = null;
  2483 + start();
  2484 + };
  2485 + img.onerror = function() {
  2486 + if (img.crossOrigin === "anonymous") {
  2487 + // CORS failed
  2488 + window.clearTimeout( imageObj.timer );
  2489 +
  2490 + // let's try with proxy instead
  2491 + if ( options.proxy ) {
  2492 + var src = img.src;
  2493 + img = new Image();
  2494 + imageObj.img = img;
  2495 + img.src = src;
  2496 +
  2497 + proxyGetImage( img.src, img, imageObj );
  2498 + return;
  2499 + }
  2500 + }
  2501 +
  2502 + images.numLoaded++;
  2503 + images.numFailed++;
  2504 + imageObj.succeeded = false;
  2505 + img.onerror = img.onload = null;
  2506 + start();
  2507 + };
  2508 + }
  2509 +
  2510 + methods = {
  2511 + loadImage: function( src ) {
  2512 + var img, imageObj;
  2513 + if ( src && images[src] === undefined ) {
  2514 + img = new Image();
  2515 + if ( src.match(/data:image\/.*;base64,/i) ) {
  2516 + img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
  2517 + imageObj = images[src] = {
  2518 + img: img
  2519 + };
  2520 + images.numTotal++;
  2521 + setImageLoadHandlers(img, imageObj);
  2522 + } else if ( isSameOrigin( src ) || options.allowTaint === true ) {
  2523 + imageObj = images[src] = {
  2524 + img: img
  2525 + };
  2526 + images.numTotal++;
  2527 + setImageLoadHandlers(img, imageObj);
  2528 + img.src = src;
  2529 + } else if ( supportCORS && !options.allowTaint && options.useCORS ) {
  2530 + // attempt to load with CORS
  2531 +
  2532 + img.crossOrigin = "anonymous";
  2533 + imageObj = images[src] = {
  2534 + img: img
  2535 + };
  2536 + images.numTotal++;
  2537 + setImageLoadHandlers(img, imageObj);
  2538 + img.src = src;
  2539 + } else if ( options.proxy ) {
  2540 + imageObj = images[src] = {
  2541 + img: img
  2542 + };
  2543 + images.numTotal++;
  2544 + proxyGetImage( src, img, imageObj );
  2545 + }
  2546 + }
  2547 +
  2548 + },
  2549 + cleanupDOM: function(cause) {
  2550 + var img, src;
  2551 + if (!images.cleanupDone) {
  2552 + if (cause && typeof cause === "string") {
  2553 + Util.log("html2canvas: Cleanup because: " + cause);
  2554 + } else {
  2555 + Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
  2556 + }
  2557 +
  2558 + for (src in images) {
  2559 + if (images.hasOwnProperty(src)) {
  2560 + img = images[src];
  2561 + if (typeof img === "object" && img.callbackname && img.succeeded === undefined) {
  2562 + // cancel proxy image request
  2563 + 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)
  2564 + try {
  2565 + delete window[img.callbackname]; // for all browser that support this
  2566 + } catch(ex) {}
  2567 + if (img.script && img.script.parentNode) {
  2568 + img.script.setAttribute("src", "about:blank"); // try to cancel running request
  2569 + img.script.parentNode.removeChild(img.script);
  2570 + }
  2571 + images.numLoaded++;
  2572 + images.numFailed++;
  2573 + Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
  2574 + }
  2575 + }
  2576 + }
  2577 +
  2578 + // cancel any pending requests
  2579 + if(window.stop !== undefined) {
  2580 + window.stop();
  2581 + } else if(document.execCommand !== undefined) {
  2582 + document.execCommand("Stop", false);
  2583 + }
  2584 + if (document.close !== undefined) {
  2585 + document.close();
  2586 + }
  2587 + images.cleanupDone = true;
  2588 + if (!(cause && typeof cause === "string")) {
  2589 + start();
  2590 + }
  2591 + }
  2592 + },
  2593 +
  2594 + renderingDone: function() {
  2595 + if (timeoutTimer) {
  2596 + window.clearTimeout(timeoutTimer);
  2597 + }
  2598 + }
  2599 + };
  2600 +
  2601 + if (options.timeout > 0) {
  2602 + timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
  2603 + }
  2604 +
  2605 + Util.log('html2canvas: Preload starts: finding background-images');
  2606 + images.firstRun = true;
  2607 +
  2608 + getImages(element);
  2609 +
  2610 + Util.log('html2canvas: Preload: Finding images');
  2611 + // load <img> images
  2612 + for (i = 0; i < imgLen; i+=1){
  2613 + methods.loadImage( domImages[i].getAttribute( "src" ) );
  2614 + }
  2615 +
  2616 + images.firstRun = false;
  2617 + Util.log('html2canvas: Preload: Done.');
  2618 + if (images.numTotal === images.numLoaded) {
  2619 + start();
  2620 + }
  2621 +
  2622 + return methods;
  2623 +};
  2624 +
  2625 +_html2canvas.Renderer = function(parseQueue, options){
  2626 + function sortZindex(a, b) {
  2627 + if (a === 'children') {
  2628 + return -1;
  2629 + } else if (b === 'children') {
  2630 + return 1;
  2631 + } else {
  2632 + return a - b;
  2633 + }
  2634 + }
  2635 +
  2636 + // http://www.w3.org/TR/CSS21/zindex.html
  2637 + function createRenderQueue(parseQueue) {
  2638 + var queue = [],
  2639 + rootContext;
  2640 +
  2641 + rootContext = (function buildStackingContext(rootNode) {
  2642 + var rootContext = {};
  2643 + function insert(context, node, specialParent) {
  2644 + var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
  2645 + contextForChildren = context, // the stacking context for children
  2646 + isPositioned = node.zIndex.isPositioned,
  2647 + isFloated = node.zIndex.isFloated,
  2648 + stub = {node: node},
  2649 + childrenDest = specialParent; // where children without z-index should be pushed into
  2650 +
  2651 + if (node.zIndex.ownStacking) {
  2652 + contextForChildren = stub.context = {
  2653 + children: [{node:node, children: []}]
  2654 + };
  2655 + childrenDest = undefined;
  2656 + } else if (isPositioned || isFloated) {
  2657 + childrenDest = stub.children = [];
  2658 + }
  2659 +
  2660 + if (zi === 0 && specialParent) {
  2661 + specialParent.push(stub);
  2662 + } else {
  2663 + if (!context[zi]) { context[zi] = []; }
  2664 + context[zi].push(stub);
  2665 + }
  2666 +
  2667 + node.zIndex.children.forEach(function(childNode) {
  2668 + insert(contextForChildren, childNode, childrenDest);
  2669 + });
  2670 + }
  2671 + insert(rootContext, rootNode);
  2672 + return rootContext;
  2673 + })(parseQueue);
  2674 +
  2675 + function sortZ(context) {
  2676 + Object.keys(context).sort(sortZindex).forEach(function(zi) {
  2677 + var nonPositioned = [],
  2678 + floated = [],
  2679 + positioned = [],
  2680 + list = [];
  2681 +
  2682 + // positioned after static
  2683 + context[zi].forEach(function(v) {
  2684 + if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
  2685 + // http://www.w3.org/TR/css3-color/#transparency
  2686 + // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’.
  2687 + positioned.push(v);
  2688 + } else if (v.node.zIndex.isFloated) {
  2689 + floated.push(v);
  2690 + } else {
  2691 + nonPositioned.push(v);
  2692 + }
  2693 + });
  2694 +
  2695 + (function walk(arr) {
  2696 + arr.forEach(function(v) {
  2697 + list.push(v);
  2698 + if (v.children) { walk(v.children); }
  2699 + });
  2700 + })(nonPositioned.concat(floated, positioned));
  2701 +
  2702 + list.forEach(function(v) {
  2703 + if (v.context) {
  2704 + sortZ(v.context);
  2705 + } else {
  2706 + queue.push(v.node);
  2707 + }
  2708 + });
  2709 + });
  2710 + }
  2711 +
  2712 + sortZ(rootContext);
  2713 +
  2714 + return queue;
  2715 + }
  2716 +
  2717 + function getRenderer(rendererName) {
  2718 + var renderer;
  2719 +
  2720 + if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
  2721 + renderer = _html2canvas.Renderer[rendererName](options);
  2722 + } else if (typeof rendererName === "function") {
  2723 + renderer = rendererName(options);
  2724 + } else {
  2725 + throw new Error("Unknown renderer");
  2726 + }
  2727 +
  2728 + if ( typeof renderer !== "function" ) {
  2729 + throw new Error("Invalid renderer defined");
  2730 + }
  2731 + return renderer;
  2732 + }
  2733 +
  2734 + return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
  2735 +};
  2736 +
  2737 +_html2canvas.Util.Support = function (options, doc) {
  2738 +
  2739 + function supportSVGRendering() {
  2740 + var img = new Image(),
  2741 + canvas = doc.createElement("canvas"),
  2742 + ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
  2743 + if (ctx === false) {
  2744 + return false;
  2745 + }
  2746 + canvas.width = canvas.height = 10;
  2747 + img.src = [
  2748 + "data:image/svg+xml,",
  2749 + "<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
  2750 + "<foreignObject width='10' height='10'>",
  2751 + "<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
  2752 + "sup",
  2753 + "</div>",
  2754 + "</foreignObject>",
  2755 + "</svg>"
  2756 + ].join("");
  2757 + try {
  2758 + ctx.drawImage(img, 0, 0);
  2759 + canvas.toDataURL();
  2760 + } catch(e) {
  2761 + return false;
  2762 + }
  2763 + _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
  2764 + return true;
  2765 + }
  2766 +
  2767 + // Test whether we can use ranges to measure bounding boxes
  2768 + // Opera doesn't provide valid bounds.height/bottom even though it supports the method.
  2769 +
  2770 + function supportRangeBounds() {
  2771 + var r, testElement, rangeBounds, rangeHeight, support = false;
  2772 +
  2773 + if (doc.createRange) {
  2774 + r = doc.createRange();
  2775 + if (r.getBoundingClientRect) {
  2776 + testElement = doc.createElement('boundtest');
  2777 + testElement.style.height = "123px";
  2778 + testElement.style.display = "block";
  2779 + doc.body.appendChild(testElement);
  2780 +
  2781 + r.selectNode(testElement);
  2782 + rangeBounds = r.getBoundingClientRect();
  2783 + rangeHeight = rangeBounds.height;
  2784 +
  2785 + if (rangeHeight === 123) {
  2786 + support = true;
  2787 + }
  2788 + doc.body.removeChild(testElement);
  2789 + }
  2790 + }
  2791 +
  2792 + return support;
  2793 + }
  2794 +
  2795 + return {
  2796 + rangeBounds: supportRangeBounds(),
  2797 + svgRendering: options.svgRendering && supportSVGRendering()
  2798 + };
  2799 +};
  2800 +window.html2canvas = function(elements, opts) {
  2801 + elements = (elements.length) ? elements : [elements];
  2802 + var queue,
  2803 + canvas,
  2804 + options = {
  2805 + // general
  2806 + logging: false,
  2807 + elements: elements,
  2808 + background: "#fff",
  2809 +
  2810 + // preload options
  2811 + proxy: null,
  2812 + timeout: 0, // no timeout
  2813 + useCORS: false, // try to load images as CORS (where available), before falling back to proxy
  2814 + allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true
  2815 +
  2816 + // parse options
  2817 + svgRendering: false, // use svg powered rendering where available (FF11+)
  2818 + ignoreElements: "IFRAME|OBJECT|PARAM",
  2819 + useOverflow: true,
  2820 + letterRendering: false,
  2821 + chinese: false,
  2822 + async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird
  2823 +
  2824 + // render options
  2825 + width: null,
  2826 + height: null,
  2827 + taintTest: true, // do a taint test with all images before applying to canvas
  2828 + renderer: "Canvas"
  2829 + };
  2830 +
  2831 + options = _html2canvas.Util.Extend(opts, options);
  2832 +
  2833 + _html2canvas.logging = options.logging;
  2834 + options.complete = function( images ) {
  2835 +
  2836 + if (typeof options.onpreloaded === "function") {
  2837 + if ( options.onpreloaded( images ) === false ) {
  2838 + return;
  2839 + }
  2840 + }
  2841 + _html2canvas.Parse( images, options, function(queue) {
  2842 + if (typeof options.onparsed === "function") {
  2843 + if ( options.onparsed( queue ) === false ) {
  2844 + return;
  2845 + }
  2846 + }
  2847 +
  2848 + canvas = _html2canvas.Renderer( queue, options );
  2849 +
  2850 + if (typeof options.onrendered === "function") {
  2851 + options.onrendered( canvas );
  2852 + }
  2853 + });
  2854 + };
  2855 +
  2856 + // for pages without images, we still want this to be async, i.e. return methods before executing
  2857 + window.setTimeout( function(){
  2858 + _html2canvas.Preload( options );
  2859 + }, 0 );
  2860 +
  2861 + return {
  2862 + render: function( queue, opts ) {
  2863 + return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) );
  2864 + },
  2865 + parse: function( images, opts ) {
  2866 + return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) );
  2867 + },
  2868 + preload: function( opts ) {
  2869 + return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
  2870 + },
  2871 + log: _html2canvas.Util.log
  2872 + };
  2873 +};
  2874 +
  2875 +window.html2canvas.log = _html2canvas.Util.log; // for renderers
  2876 +window.html2canvas.Renderer = {
  2877 + Canvas: undefined // We are assuming this will be used
  2878 +};
  2879 +_html2canvas.Renderer.Canvas = function(options) {
  2880 + options = options || {};
  2881 +
  2882 + var doc = document,
  2883 + safeImages = [],
  2884 + testCanvas = document.createElement("canvas"),
  2885 + testctx = testCanvas.getContext("2d"),
  2886 + Util = _html2canvas.Util,
  2887 + canvas = options.canvas || doc.createElement('canvas');
  2888 +
  2889 + function createShape(ctx, args) {
  2890 + ctx.beginPath();
  2891 + args.forEach(function(arg) {
  2892 + ctx[arg.name].apply(ctx, arg['arguments']);
  2893 + });
  2894 + ctx.closePath();
  2895 + }
  2896 +
  2897 + function safeImage(item) {
  2898 + if (safeImages.indexOf(item['arguments'][0].src) === -1) {
  2899 + testctx.drawImage(item['arguments'][0], 0, 0);
  2900 + try {
  2901 + testctx.getImageData(0, 0, 1, 1);
  2902 + } catch(e) {
  2903 + testCanvas = doc.createElement("canvas");
  2904 + testctx = testCanvas.getContext("2d");
  2905 + return false;
  2906 + }
  2907 + safeImages.push(item['arguments'][0].src);
  2908 + }
  2909 + return true;
  2910 + }
  2911 +
  2912 + function renderItem(ctx, item) {
  2913 + switch(item.type){
  2914 + case "variable":
  2915 + ctx[item.name] = item['arguments'];
  2916 + break;
  2917 + case "function":
  2918 + switch(item.name) {
  2919 + case "createPattern":
  2920 + if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
  2921 + try {
  2922 + ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
  2923 + } catch(e) {
  2924 + Util.log("html2canvas: Renderer: Error creating pattern", e.message);
  2925 + }
  2926 + }
  2927 + break;
  2928 + case "drawShape":
  2929 + createShape(ctx, item['arguments']);
  2930 + break;
  2931 + case "drawImage":
  2932 + if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
  2933 + if (!options.taintTest || (options.taintTest && safeImage(item))) {
  2934 + ctx.drawImage.apply( ctx, item['arguments'] );
  2935 + }
  2936 + }
  2937 + break;
  2938 + default:
  2939 + ctx[item.name].apply(ctx, item['arguments']);
  2940 + }
  2941 + break;
  2942 + }
  2943 + }
  2944 +
  2945 + return function(parsedData, options, document, queue, _html2canvas) {
  2946 + var ctx = canvas.getContext("2d"),
  2947 + newCanvas,
  2948 + bounds,
  2949 + fstyle,
  2950 + zStack = parsedData.stack;
  2951 +
  2952 + canvas.width = canvas.style.width = options.width || zStack.ctx.width;
  2953 + canvas.height = canvas.style.height = options.height || zStack.ctx.height;
  2954 +
  2955 + fstyle = ctx.fillStyle;
  2956 + ctx.fillStyle = (Util.isTransparent(parsedData.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
  2957 + ctx.fillRect(0, 0, canvas.width, canvas.height);
  2958 + ctx.fillStyle = fstyle;
  2959 + queue.forEach(function(storageContext) {
  2960 + // set common settings for canvas
  2961 + ctx.textBaseline = "bottom";
  2962 + ctx.save();
  2963 +
  2964 + if (storageContext.transform.matrix) {
  2965 + ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
  2966 + ctx.transform.apply(ctx, storageContext.transform.matrix);
  2967 + ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
  2968 + }
  2969 +
  2970 + if (storageContext.clip){
  2971 + ctx.beginPath();
  2972 + ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
  2973 + ctx.clip();
  2974 + }
  2975 +
  2976 + if (storageContext.ctx.storage) {
  2977 + storageContext.ctx.storage.forEach(function(item) {
  2978 + renderItem(ctx, item);
  2979 + });
  2980 + }
  2981 +
  2982 + ctx.restore();
  2983 + });
  2984 +
  2985 + Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
  2986 +
  2987 + if (options.elements.length === 1) {
  2988 + if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
  2989 + // crop image to the bounds of selected (single) element
  2990 + bounds = _html2canvas.Util.Bounds(options.elements[0]);
  2991 + newCanvas = document.createElement('canvas');
  2992 +
  2993 +
  2994 + newCanvas.width = Math.ceil(bounds.width);
  2995 + newCanvas.height = Math.ceil(bounds.height);
  2996 +
  2997 + ctx = newCanvas.getContext("2d");
  2998 + ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
  2999 +
  3000 +
  3001 +
  3002 + canvas = null;
  3003 + return newCanvas;
  3004 + }
  3005 + }
  3006 +
  3007 + return canvas;
  3008 + };
  3009 +};
  3010 +})(window,document);
... ...
pacotes/tableexport/jquery.base64.js 0 → 100644
... ... @@ -0,0 +1,190 @@
  1 +/*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false,
  2 + debug: false, devel: true, eqeqeq: true, es5: false, evil: false,
  3 + forin: false, fragment: false, immed: true, laxbreak: false, newcap: true,
  4 + nomen: false, on: false, onevar: true, passfail: false, plusplus: true,
  5 + regexp: false, rhino: true, safe: false, strict: false, sub: false,
  6 + undef: true, white: false, widget: false, windows: false */
  7 +/*global jQuery: false, window: false */
  8 +//"use strict";
  9 +
  10 +/*
  11 + * Original code (c) 2010 Nick Galbreath
  12 + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
  13 + *
  14 + * jQuery port (c) 2010 Carlo Zottmann
  15 + * http://github.com/carlo/jquery-base64
  16 + *
  17 + * Permission is hereby granted, free of charge, to any person
  18 + * obtaining a copy of this software and associated documentation
  19 + * files (the "Software"), to deal in the Software without
  20 + * restriction, including without limitation the rights to use,
  21 + * copy, modify, merge, publish, distribute, sublicense, and/or sell
  22 + * copies of the Software, and to permit persons to whom the
  23 + * Software is furnished to do so, subject to the following
  24 + * conditions:
  25 + *
  26 + * The above copyright notice and this permission notice shall be
  27 + * included in all copies or substantial portions of the Software.
  28 + *
  29 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  30 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  31 + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  32 + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  33 + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  34 + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  35 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  36 + * OTHER DEALINGS IN THE SOFTWARE.
  37 +*/
  38 +
  39 +/* base64 encode/decode compatible with window.btoa/atob
  40 + *
  41 + * window.atob/btoa is a Firefox extension to convert binary data (the "b")
  42 + * to base64 (ascii, the "a").
  43 + *
  44 + * It is also found in Safari and Chrome. It is not available in IE.
  45 + *
  46 + * if (!window.btoa) window.btoa = $.base64.encode
  47 + * if (!window.atob) window.atob = $.base64.decode
  48 + *
  49 + * The original spec's for atob/btoa are a bit lacking
  50 + * https://developer.mozilla.org/en/DOM/window.atob
  51 + * https://developer.mozilla.org/en/DOM/window.btoa
  52 + *
  53 + * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255]
  54 + * If any character is not [0,255], then an exception is thrown.
  55 + *
  56 + * window.atob and $.base64.decode take a base64-encoded string
  57 + * If the input length is not a multiple of 4, or contains invalid characters
  58 + * then an exception is thrown.
  59 + */
  60 +
  61 +jQuery.base64 = ( function( $ ) {
  62 +
  63 + var _PADCHAR = "=",
  64 + _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  65 + _VERSION = "1.0";
  66 +
  67 +
  68 + function _getbyte64( s, i ) {
  69 + // This is oddly fast, except on Chrome/V8.
  70 + // Minimal or no improvement in performance by using a
  71 + // object with properties mapping chars to value (eg. 'A': 0)
  72 +
  73 + var idx = _ALPHA.indexOf( s.charAt( i ) );
  74 +
  75 + if ( idx === -1 ) {
  76 + throw "Cannot decode base64";
  77 + }
  78 +
  79 + return idx;
  80 + }
  81 +
  82 +
  83 + function _decode( s ) {
  84 + var pads = 0,
  85 + i,
  86 + b10,
  87 + imax = s.length,
  88 + x = [];
  89 +
  90 + s = String( s );
  91 +
  92 + if ( imax === 0 ) {
  93 + return s;
  94 + }
  95 +
  96 + if ( imax % 4 !== 0 ) {
  97 + throw "Cannot decode base64";
  98 + }
  99 +
  100 + if ( s.charAt( imax - 1 ) === _PADCHAR ) {
  101 + pads = 1;
  102 +
  103 + if ( s.charAt( imax - 2 ) === _PADCHAR ) {
  104 + pads = 2;
  105 + }
  106 +
  107 + // either way, we want to ignore this last block
  108 + imax -= 4;
  109 + }
  110 +
  111 + for ( i = 0; i < imax; i += 4 ) {
  112 + b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 );
  113 + x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) );
  114 + }
  115 +
  116 + switch ( pads ) {
  117 + case 1:
  118 + b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 );
  119 + x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) );
  120 + break;
  121 +
  122 + case 2:
  123 + b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 );
  124 + x.push( String.fromCharCode( b10 >> 16 ) );
  125 + break;
  126 + }
  127 +
  128 + return x.join( "" );
  129 + }
  130 +
  131 +
  132 + function _getbyte( s, i ) {
  133 + var x = s.charCodeAt( i );
  134 +
  135 + if ( x > 255 ) {
  136 + throw "INVALID_CHARACTER_ERR: DOM Exception 5";
  137 + }
  138 +
  139 + return x;
  140 + }
  141 +
  142 +
  143 + function _encode( s ) {
  144 + if ( arguments.length !== 1 ) {
  145 + throw "SyntaxError: exactly one argument required";
  146 + }
  147 +
  148 + s = String( s );
  149 +
  150 + var i,
  151 + b10,
  152 + x = [],
  153 + imax = s.length - s.length % 3;
  154 +
  155 + if ( s.length === 0 ) {
  156 + return s;
  157 + }
  158 +
  159 + for ( i = 0; i < imax; i += 3 ) {
  160 + b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 );
  161 + x.push( _ALPHA.charAt( b10 >> 18 ) );
  162 + x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) );
  163 + x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) );
  164 + x.push( _ALPHA.charAt( b10 & 0x3f ) );
  165 + }
  166 +
  167 + switch ( s.length - imax ) {
  168 + case 1:
  169 + b10 = _getbyte( s, i ) << 16;
  170 + x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR );
  171 + break;
  172 +
  173 + case 2:
  174 + b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 );
  175 + x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR );
  176 + break;
  177 + }
  178 +
  179 + return x.join( "" );
  180 + }
  181 +
  182 +
  183 + return {
  184 + decode: _decode,
  185 + encode: _encode,
  186 + VERSION: _VERSION
  187 + };
  188 +
  189 +}( jQuery ) );
  190 +
... ...
pacotes/tableexport/jspdf/jspdf.js 0 → 100644
... ... @@ -0,0 +1,303 @@
  1 +/**
  2 + * jsPDF
  3 + * (c) 2009 James Hall
  4 + *
  5 + * Some parts based on FPDF.
  6 + */
  7 +
  8 +var jsPDF = function(){
  9 +
  10 + // Private properties
  11 + var version = '20090504';
  12 + var buffer = '';
  13 +
  14 + var pdfVersion = '1.3'; // PDF Version
  15 + var defaultPageFormat = 'a4';
  16 + var pageFormats = { // Size in mm of various paper formats
  17 + 'a3': [841.89, 1190.55],
  18 + 'a4': [595.28, 841.89],
  19 + 'a5': [420.94, 595.28],
  20 + 'letter': [612, 792],
  21 + 'legal': [612, 1008]
  22 + };
  23 + var textColor = '0 g';
  24 + var page = 0;
  25 + var objectNumber = 2; // 'n' Current object number
  26 + var state = 0; // Current document state
  27 + var pages = new Array();
  28 + var offsets = new Array(); // List of offsets
  29 + var lineWidth = 0.200025; // 2mm
  30 + var pageHeight;
  31 + var k; // Scale factor
  32 + var unit = 'mm'; // Default to mm for units
  33 + var fontNumber; // TODO: This is temp, replace with real font handling
  34 + var documentProperties = {};
  35 + var fontSize = 16; // Default font size
  36 + var pageFontSize = 16;
  37 +
  38 + // Initilisation
  39 + if (unit == 'pt') {
  40 + k = 1;
  41 + } else if(unit == 'mm') {
  42 + k = 72/25.4;
  43 + } else if(unit == 'cm') {
  44 + k = 72/2.54;
  45 + } else if(unit == 'in') {
  46 + k = 72;
  47 + }
  48 +
  49 + // Private functions
  50 + var newObject = function() {
  51 + //Begin a new object
  52 + objectNumber ++;
  53 + offsets[objectNumber] = buffer.length;
  54 + out(objectNumber + ' 0 obj');
  55 + }
  56 +
  57 +
  58 + var putHeader = function() {
  59 + out('%PDF-' + pdfVersion);
  60 + }
  61 +
  62 + var putPages = function() {
  63 +
  64 + // TODO: Fix, hardcoded to a4 portrait
  65 + var wPt = pageWidth * k;
  66 + var hPt = pageHeight * k;
  67 +
  68 + for(n=1; n <= page; n++) {
  69 + newObject();
  70 + out('<</Type /Page');
  71 + out('/Parent 1 0 R');
  72 + out('/Resources 2 0 R');
  73 + out('/Contents ' + (objectNumber + 1) + ' 0 R>>');
  74 + out('endobj');
  75 +
  76 + //Page content
  77 + p = pages[n];
  78 + newObject();
  79 + out('<</Length ' + p.length + '>>');
  80 + putStream(p);
  81 + out('endobj');
  82 + }
  83 + offsets[1] = buffer.length;
  84 + out('1 0 obj');
  85 + out('<</Type /Pages');
  86 + var kids='/Kids [';
  87 + for (i = 0; i < page; i++) {
  88 + kids += (3 + 2 * i) + ' 0 R ';
  89 + }
  90 + out(kids + ']');
  91 + out('/Count ' + page);
  92 + out(sprintf('/MediaBox [0 0 %.2f %.2f]', wPt, hPt));
  93 + out('>>');
  94 + out('endobj');
  95 + }
  96 +
  97 + var putStream = function(str) {
  98 + out('stream');
  99 + out(str);
  100 + out('endstream');
  101 + }
  102 +
  103 + var putResources = function() {
  104 + putFonts();
  105 + putImages();
  106 +
  107 + //Resource dictionary
  108 + offsets[2] = buffer.length;
  109 + out('2 0 obj');
  110 + out('<<');
  111 + putResourceDictionary();
  112 + out('>>');
  113 + out('endobj');
  114 + }
  115 +
  116 + var putFonts = function() {
  117 + // TODO: Only supports core font hardcoded to Helvetica
  118 + newObject();
  119 + fontNumber = objectNumber;
  120 + name = 'Helvetica';
  121 + out('<</Type /Font');
  122 + out('/BaseFont /' + name);
  123 + out('/Subtype /Type1');
  124 + out('/Encoding /WinAnsiEncoding');
  125 + out('>>');
  126 + out('endobj');
  127 + }
  128 +
  129 + var putImages = function() {
  130 + // TODO
  131 + }
  132 +
  133 + var putResourceDictionary = function() {
  134 + out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
  135 + out('/Font <<');
  136 + // Do this for each font, the '1' bit is the index of the font
  137 + // fontNumber is currently the object number related to 'putFonts'
  138 + out('/F1 ' + fontNumber + ' 0 R');
  139 + out('>>');
  140 + out('/XObject <<');
  141 + putXobjectDict();
  142 + out('>>');
  143 + }
  144 +
  145 + var putXobjectDict = function() {
  146 + // TODO
  147 + // Loop through images
  148 + }
  149 +
  150 +
  151 + var putInfo = function() {
  152 + out('/Producer (jsPDF ' + version + ')');
  153 + if(documentProperties.title != undefined) {
  154 + out('/Title (' + pdfEscape(documentProperties.title) + ')');
  155 + }
  156 + if(documentProperties.subject != undefined) {
  157 + out('/Subject (' + pdfEscape(documentProperties.subject) + ')');
  158 + }
  159 + if(documentProperties.author != undefined) {
  160 + out('/Author (' + pdfEscape(documentProperties.author) + ')');
  161 + }
  162 + if(documentProperties.keywords != undefined) {
  163 + out('/Keywords (' + pdfEscape(documentProperties.keywords) + ')');
  164 + }
  165 + if(documentProperties.creator != undefined) {
  166 + out('/Creator (' + pdfEscape(documentProperties.creator) + ')');
  167 + }
  168 + var created = new Date();
  169 + var year = created.getFullYear();
  170 + var month = (created.getMonth() + 1);
  171 + var day = created.getDate();
  172 + var hour = created.getHours();
  173 + var minute = created.getMinutes();
  174 + var second = created.getSeconds();
  175 + out('/CreationDate (D:' + sprintf('%02d%02d%02d%02d%02d%02d', year, month, day, hour, minute, second) + ')');
  176 + }
  177 +
  178 + var putCatalog = function () {
  179 + out('/Type /Catalog');
  180 + out('/Pages 1 0 R');
  181 + // TODO: Add zoom and layout modes
  182 + out('/OpenAction [3 0 R /FitH null]');
  183 + out('/PageLayout /OneColumn');
  184 + }
  185 +
  186 + function putTrailer() {
  187 + out('/Size ' + (objectNumber + 1));
  188 + out('/Root ' + objectNumber + ' 0 R');
  189 + out('/Info ' + (objectNumber - 1) + ' 0 R');
  190 + }
  191 +
  192 + var endDocument = function() {
  193 + state = 1;
  194 + putHeader();
  195 + putPages();
  196 +
  197 + putResources();
  198 + //Info
  199 + newObject();
  200 + out('<<');
  201 + putInfo();
  202 + out('>>');
  203 + out('endobj');
  204 +
  205 + //Catalog
  206 + newObject();
  207 + out('<<');
  208 + putCatalog();
  209 + out('>>');
  210 + out('endobj');
  211 +
  212 + //Cross-ref
  213 + var o = buffer.length;
  214 + out('xref');
  215 + out('0 ' + (objectNumber + 1));
  216 + out('0000000000 65535 f ');
  217 + for (var i=1; i <= objectNumber; i++) {
  218 + out(sprintf('%010d 00000 n ', offsets[i]));
  219 + }
  220 + //Trailer
  221 + out('trailer');
  222 + out('<<');
  223 + putTrailer();
  224 + out('>>');
  225 + out('startxref');
  226 + out(o);
  227 + out('%%EOF');
  228 + state = 3;
  229 + }
  230 +
  231 + var beginPage = function() {
  232 + page ++;
  233 + // Do dimension stuff
  234 + state = 2;
  235 + pages[page] = '';
  236 +
  237 + // TODO: Hardcoded at A4 and portrait
  238 + pageHeight = pageFormats['a4'][1] / k;
  239 + pageWidth = pageFormats['a4'][0] / k;
  240 + }
  241 +
  242 + var out = function(string) {
  243 + if(state == 2) {
  244 + pages[page] += string + '\n';
  245 + } else {
  246 + buffer += string + '\n';
  247 + }
  248 + }
  249 +
  250 + var _addPage = function() {
  251 + beginPage();
  252 + // Set line width
  253 + out(sprintf('%.2f w', (lineWidth * k)));
  254 +
  255 + // Set font - TODO
  256 + // 16 is the font size
  257 + pageFontSize = fontSize;
  258 + out('BT /F1 ' + parseInt(fontSize) + '.00 Tf ET');
  259 + }
  260 +
  261 + // Add the first page automatically
  262 + _addPage();
  263 +
  264 + // Escape text
  265 + var pdfEscape = function(text) {
  266 + return text.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
  267 + }
  268 +
  269 + return {
  270 + addPage: function() {
  271 + _addPage();
  272 + },
  273 + text: function(x, y, text) {
  274 + // need page height
  275 + if(pageFontSize != fontSize) {
  276 + out('BT /F1 ' + parseInt(fontSize) + '.00 Tf ET');
  277 + pageFontSize = fontSize;
  278 + }
  279 + var str = sprintf('BT %.2f %.2f Td (%s) Tj ET', x * k, (pageHeight - y) * k, pdfEscape(text));
  280 + out(str);
  281 + },
  282 + setProperties: function(properties) {
  283 + documentProperties = properties;
  284 + },
  285 + addImage: function(imageData, format, x, y, w, h) {
  286 +
  287 + },
  288 + output: function(type, options) {
  289 + endDocument();
  290 + if(type == undefined) {
  291 + return buffer;
  292 + }
  293 + if(type == 'datauri') {
  294 + document.location.href = 'data:application/pdf;base64,' + Base64.encode(buffer);
  295 + }
  296 + // @TODO: Add different output options
  297 + },
  298 + setFontSize: function(size) {
  299 + fontSize = size;
  300 + }
  301 + }
  302 +
  303 +};
... ...
pacotes/tableexport/jspdf/libs/base64.js 0 → 100644
... ... @@ -0,0 +1,143 @@
  1 +
  2 +/**
  3 +*
  4 +* Base64 encode / decode
  5 +* http://www.webtoolkit.info/
  6 +*
  7 +**/
  8 +
  9 +var Base64 = {
  10 +
  11 + // private property
  12 + _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  13 +
  14 + // public method for encoding
  15 + encode : function (input) {
  16 + var output = "";
  17 + var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  18 + var i = 0;
  19 +
  20 + input = Base64._utf8_encode(input);
  21 +
  22 + while (i < input.length) {
  23 +
  24 + chr1 = input.charCodeAt(i++);
  25 + chr2 = input.charCodeAt(i++);
  26 + chr3 = input.charCodeAt(i++);
  27 +
  28 + enc1 = chr1 >> 2;
  29 + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  30 + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  31 + enc4 = chr3 & 63;
  32 +
  33 + if (isNaN(chr2)) {
  34 + enc3 = enc4 = 64;
  35 + } else if (isNaN(chr3)) {
  36 + enc4 = 64;
  37 + }
  38 +
  39 + output = output +
  40 + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
  41 + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
  42 +
  43 + }
  44 +
  45 + return output;
  46 + },
  47 +
  48 + // public method for decoding
  49 + decode : function (input) {
  50 + var output = "";
  51 + var chr1, chr2, chr3;
  52 + var enc1, enc2, enc3, enc4;
  53 + var i = 0;
  54 +
  55 + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  56 +
  57 + while (i < input.length) {
  58 +
  59 + enc1 = this._keyStr.indexOf(input.charAt(i++));
  60 + enc2 = this._keyStr.indexOf(input.charAt(i++));
  61 + enc3 = this._keyStr.indexOf(input.charAt(i++));
  62 + enc4 = this._keyStr.indexOf(input.charAt(i++));
  63 +
  64 + chr1 = (enc1 << 2) | (enc2 >> 4);
  65 + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  66 + chr3 = ((enc3 & 3) << 6) | enc4;
  67 +
  68 + output = output + String.fromCharCode(chr1);
  69 +
  70 + if (enc3 != 64) {
  71 + output = output + String.fromCharCode(chr2);
  72 + }
  73 + if (enc4 != 64) {
  74 + output = output + String.fromCharCode(chr3);
  75 + }
  76 +
  77 + }
  78 +
  79 + output = Base64._utf8_decode(output);
  80 +
  81 + return output;
  82 +
  83 + },
  84 +
  85 + // private method for UTF-8 encoding
  86 + _utf8_encode : function (string) {
  87 + string = string.replace(/\r\n/g,"\n");
  88 + var utftext = "";
  89 +
  90 + for (var n = 0; n < string.length; n++) {
  91 +
  92 + var c = string.charCodeAt(n);
  93 +
  94 + if (c < 128) {
  95 + utftext += String.fromCharCode(c);
  96 + }
  97 + else if((c > 127) && (c < 2048)) {
  98 + utftext += String.fromCharCode((c >> 6) | 192);
  99 + utftext += String.fromCharCode((c & 63) | 128);
  100 + }
  101 + else {
  102 + utftext += String.fromCharCode((c >> 12) | 224);
  103 + utftext += String.fromCharCode(((c >> 6) & 63) | 128);
  104 + utftext += String.fromCharCode((c & 63) | 128);
  105 + }
  106 +
  107 + }
  108 +
  109 + return utftext;
  110 + },
  111 +
  112 + // private method for UTF-8 decoding
  113 + _utf8_decode : function (utftext) {
  114 + var string = "";
  115 + var i = 0;
  116 + var c = c1 = c2 = 0;
  117 +
  118 + while ( i < utftext.length ) {
  119 +
  120 + c = utftext.charCodeAt(i);
  121 +
  122 + if (c < 128) {
  123 + string += String.fromCharCode(c);
  124 + i++;
  125 + }
  126 + else if((c > 191) && (c < 224)) {
  127 + c2 = utftext.charCodeAt(i+1);
  128 + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  129 + i += 2;
  130 + }
  131 + else {
  132 + c2 = utftext.charCodeAt(i+1);
  133 + c3 = utftext.charCodeAt(i+2);
  134 + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  135 + i += 3;
  136 + }
  137 +
  138 + }
  139 +
  140 + return string;
  141 + }
  142 +
  143 +}
... ...
pacotes/tableexport/jspdf/libs/sprintf.js 0 → 100644
... ... @@ -0,0 +1,152 @@
  1 +
  2 +
  3 +function sprintf( ) {
  4 + // Return a formatted string
  5 + //
  6 + // version: 903.3016
  7 + // discuss at: http://phpjs.org/functions/sprintf
  8 + // + original by: Ash Searle (http://hexmen.com/blog/)
  9 + // + namespaced by: Michael White (http://getsprink.com)
  10 + // + tweaked by: Jack
  11 + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  12 + // + input by: Paulo Ricardo F. Santos
  13 + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  14 + // + input by: Brett Zamir (http://brettz9.blogspot.com)
  15 + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  16 + // * example 1: sprintf("%01.2f", 123.1);
  17 + // * returns 1: 123.10
  18 + // * example 2: sprintf("[%10s]", 'monkey');
  19 + // * returns 2: '[ monkey]'
  20 + // * example 3: sprintf("[%'#10s]", 'monkey');
  21 + // * returns 3: '[####monkey]'
  22 + var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
  23 + var a = arguments, i = 0, format = a[i++];
  24 +
  25 + // pad()
  26 + var pad = function(str, len, chr, leftJustify) {
  27 + if (!chr) chr = ' ';
  28 + var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
  29 + return leftJustify ? str + padding : padding + str;
  30 + };
  31 +
  32 + // justify()
  33 + var justify = function(value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
  34 + var diff = minWidth - value.length;
  35 + if (diff > 0) {
  36 + if (leftJustify || !zeroPad) {
  37 + value = pad(value, minWidth, customPadChar, leftJustify);
  38 + } else {
  39 + value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
  40 + }
  41 + }
  42 + return value;
  43 + };
  44 +
  45 + // formatBaseX()
  46 + var formatBaseX = function(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
  47 + // Note: casts negative numbers to positive ones
  48 + var number = value >>> 0;
  49 + prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
  50 + value = prefix + pad(number.toString(base), precision || 0, '0', false);
  51 + return justify(value, prefix, leftJustify, minWidth, zeroPad);
  52 + };
  53 +
  54 + // formatString()
  55 + var formatString = function(value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
  56 + if (precision != null) {
  57 + value = value.slice(0, precision);
  58 + }
  59 + return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
  60 + };
  61 +
  62 + // doFormat()
  63 + var doFormat = function(substring, valueIndex, flags, minWidth, _, precision, type) {
  64 + var number;
  65 + var prefix;
  66 + var method;
  67 + var textTransform;
  68 + var value;
  69 +
  70 + if (substring == '%%') return '%';
  71 +
  72 + // parse flags
  73 + var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, customPadChar = ' ';
  74 + var flagsl = flags.length;
  75 + for (var j = 0; flags && j < flagsl; j++) switch (flags.charAt(j)) {
  76 + case ' ': positivePrefix = ' '; break;
  77 + case '+': positivePrefix = '+'; break;
  78 + case '-': leftJustify = true; break;
  79 + case "'": customPadChar = flags.charAt(j+1); break;
  80 + case '0': zeroPad = true; break;
  81 + case '#': prefixBaseX = true; break;
  82 + }
  83 +
  84 + // parameters may be null, undefined, empty-string or real valued
  85 + // we want to ignore null, undefined and empty-string values
  86 + if (!minWidth) {
  87 + minWidth = 0;
  88 + } else if (minWidth == '*') {
  89 + minWidth = +a[i++];
  90 + } else if (minWidth.charAt(0) == '*') {
  91 + minWidth = +a[minWidth.slice(1, -1)];
  92 + } else {
  93 + minWidth = +minWidth;
  94 + }
  95 +
  96 + // Note: undocumented perl feature:
  97 + if (minWidth < 0) {
  98 + minWidth = -minWidth;
  99 + leftJustify = true;
  100 + }
  101 +
  102 + if (!isFinite(minWidth)) {
  103 + throw new Error('sprintf: (minimum-)width must be finite');
  104 + }
  105 +
  106 + if (!precision) {
  107 + precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
  108 + } else if (precision == '*') {
  109 + precision = +a[i++];
  110 + } else if (precision.charAt(0) == '*') {
  111 + precision = +a[precision.slice(1, -1)];
  112 + } else {
  113 + precision = +precision;
  114 + }
  115 +
  116 + // grab value using valueIndex if required?
  117 + value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
  118 +
  119 + switch (type) {
  120 + case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
  121 + case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
  122 + case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
  123 + case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
  124 + case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
  125 + case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
  126 + case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
  127 + case 'i':
  128 + case 'd': {
  129 + number = parseInt(+value);
  130 + prefix = number < 0 ? '-' : positivePrefix;
  131 + value = prefix + pad(String(Math.abs(number)), precision, '0', false);
  132 + return justify(value, prefix, leftJustify, minWidth, zeroPad);
  133 + }
  134 + case 'e':
  135 + case 'E':
  136 + case 'f':
  137 + case 'F':
  138 + case 'g':
  139 + case 'G': {
  140 + number = +value;
  141 + prefix = number < 0 ? '-' : positivePrefix;
  142 + method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
  143 + textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
  144 + value = prefix + Math.abs(number)[method](precision);
  145 + return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
  146 + }
  147 + default: return substring;
  148 + }
  149 + };
  150 +
  151 + return format.replace(regex, doFormat);
  152 +}
... ...
pacotes/tableexport/tableExport.jquery.json 0 → 100644
No preview for this file type
pacotes/tableexport/tableExport.js 0 → 100644
... ... @@ -0,0 +1,359 @@
  1 +/*The MIT License (MIT)
  2 +
  3 +Copyright (c) 2014 https://github.com/kayalshri/
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in
  13 +all copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21 +THE SOFTWARE.*/
  22 +
  23 +(function($){
  24 + $.fn.extend({
  25 + tableExport: function(options) {
  26 + var defaults = {
  27 + separator: ',',
  28 + ignoreColumn: [],
  29 + tableName:'yourTableName',
  30 + type:'csv',
  31 + pdfFontSize:14,
  32 + pdfLeftMargin:20,
  33 + escape:'true',
  34 + htmlContent:'false',
  35 + consoleLog:'false'
  36 + };
  37 +
  38 + var options = $.extend(defaults, options);
  39 + var el = this;
  40 +
  41 + if(defaults.type == 'csv' || defaults.type == 'txt'){
  42 +
  43 + // Header
  44 + var tdData ="";
  45 + $(el).find('thead').find('tr').each(function() {
  46 + tdData += "\n";
  47 + $(this).filter(':visible').find('th').each(function(index,data) {
  48 + if ($(this).css('display') != 'none'){
  49 + if(defaults.ignoreColumn.indexOf(index) == -1){
  50 + tdData += '"' + parseString($(this)) + '"' + defaults.separator;
  51 + }
  52 + }
  53 +
  54 + });
  55 + tdData = $.trim(tdData);
  56 + tdData = $.trim(tdData).substring(0, tdData.length -1);
  57 + });
  58 +
  59 + // Row vs Column
  60 + $(el).find('tbody').find('tr').each(function() {
  61 + tdData += "\n";
  62 + $(this).filter(':visible').find('td').each(function(index,data) {
  63 + if ($(this).css('display') != 'none'){
  64 + if(defaults.ignoreColumn.indexOf(index) == -1){
  65 + tdData += '"'+ parseString($(this)) + '"'+ defaults.separator;
  66 + }
  67 + }
  68 + });
  69 + //tdData = $.trim(tdData);
  70 + tdData = $.trim(tdData).substring(0, tdData.length -1);
  71 + });
  72 +
  73 + //output
  74 + if(defaults.consoleLog == 'true'){
  75 + console.log(tdData);
  76 + }
  77 + var base64data = "base64," + $.base64.encode(tdData);
  78 + window.open('data:application/'+defaults.type+';filename=exportData;' + base64data);
  79 + }else if(defaults.type == 'sql'){
  80 +
  81 + // Header
  82 + var tdData ="INSERT INTO `"+defaults.tableName+"` (";
  83 + $(el).find('thead').find('tr').each(function() {
  84 +
  85 + $(this).filter(':visible').find('th').each(function(index,data) {
  86 + if ($(this).css('display') != 'none'){
  87 + if(defaults.ignoreColumn.indexOf(index) == -1){
  88 + tdData += '`' + parseString($(this)) + '`,' ;
  89 + }
  90 + }
  91 +
  92 + });
  93 + tdData = $.trim(tdData);
  94 + tdData = $.trim(tdData).substring(0, tdData.length -1);
  95 + });
  96 + tdData += ") VALUES ";
  97 + // Row vs Column
  98 + $(el).find('tbody').find('tr').each(function() {
  99 + tdData += "(";
  100 + $(this).filter(':visible').find('td').each(function(index,data) {
  101 + if ($(this).css('display') != 'none'){
  102 + if(defaults.ignoreColumn.indexOf(index) == -1){
  103 + tdData += '"'+ parseString($(this)) + '",';
  104 + }
  105 + }
  106 + });
  107 +
  108 + tdData = $.trim(tdData).substring(0, tdData.length -1);
  109 + tdData += "),";
  110 + });
  111 + tdData = $.trim(tdData).substring(0, tdData.length -1);
  112 + tdData += ";";
  113 +
  114 + //output
  115 + //console.log(tdData);
  116 +
  117 + if(defaults.consoleLog == 'true'){
  118 + console.log(tdData);
  119 + }
  120 +
  121 + var base64data = "base64," + $.base64.encode(tdData);
  122 + window.open('data:application/sql;filename=exportData;' + base64data);
  123 +
  124 +
  125 + }else if(defaults.type == 'json'){
  126 +
  127 + var jsonHeaderArray = [];
  128 + $(el).find('thead').find('tr').each(function() {
  129 + var tdData ="";
  130 + var jsonArrayTd = [];
  131 +
  132 + $(this).filter(':visible').find('th').each(function(index,data) {
  133 + if ($(this).css('display') != 'none'){
  134 + if(defaults.ignoreColumn.indexOf(index) == -1){
  135 + jsonArrayTd.push(parseString($(this)));
  136 + }
  137 + }
  138 + });
  139 + jsonHeaderArray.push(jsonArrayTd);
  140 +
  141 + });
  142 +
  143 + var jsonArray = [];
  144 + $(el).find('tbody').find('tr').each(function() {
  145 + var tdData ="";
  146 + var jsonArrayTd = [];
  147 +
  148 + $(this).filter(':visible').find('td').each(function(index,data) {
  149 + if ($(this).css('display') != 'none'){
  150 + if(defaults.ignoreColumn.indexOf(index) == -1){
  151 + jsonArrayTd.push(parseString($(this)));
  152 + }
  153 + }
  154 + });
  155 + jsonArray.push(jsonArrayTd);
  156 +
  157 + });
  158 +
  159 + var jsonExportArray =[];
  160 + jsonExportArray.push({header:jsonHeaderArray,data:jsonArray});
  161 +
  162 + //Return as JSON
  163 + //console.log(JSON.stringify(jsonExportArray));
  164 +
  165 + //Return as Array
  166 + //console.log(jsonExportArray);
  167 + if(defaults.consoleLog == 'true'){
  168 + console.log(JSON.stringify(jsonExportArray));
  169 + }
  170 + var base64data = "base64," + $.base64.encode(JSON.stringify(jsonExportArray));
  171 + window.open('data:application/json;filename=exportData;' + base64data);
  172 + }else if(defaults.type == 'xml'){
  173 +
  174 + var xml = '<?xml version="1.0" encoding="utf-8"?>';
  175 + xml += '<tabledata><fields>';
  176 +
  177 + // Header
  178 + $(el).find('thead').find('tr').each(function() {
  179 + $(this).filter(':visible').find('th').each(function(index,data) {
  180 + if ($(this).css('display') != 'none'){
  181 + if(defaults.ignoreColumn.indexOf(index) == -1){
  182 + xml += "<field>" + parseString($(this)) + "</field>";
  183 + }
  184 + }
  185 + });
  186 + });
  187 + xml += '</fields><data>';
  188 +
  189 + // Row Vs Column
  190 + var rowCount=1;
  191 + $(el).find('tbody').find('tr').each(function() {
  192 + xml += '<row id="'+rowCount+'">';
  193 + var colCount=0;
  194 + $(this).filter(':visible').find('td').each(function(index,data) {
  195 + if ($(this).css('display') != 'none'){
  196 + if(defaults.ignoreColumn.indexOf(index) == -1){
  197 + xml += "<column-"+colCount+">"+parseString($(this))+"</column-"+colCount+">";
  198 + }
  199 + }
  200 + colCount++;
  201 + });
  202 + rowCount++;
  203 + xml += '</row>';
  204 + });
  205 + xml += '</data></tabledata>'
  206 +
  207 + if(defaults.consoleLog == 'true'){
  208 + console.log(xml);
  209 + }
  210 +
  211 + var base64data = "base64," + $.base64.encode(xml);
  212 + window.open('data:application/xml;filename=exportData;' + base64data);
  213 +
  214 + }else if(defaults.type == 'excel' || defaults.type == 'doc'|| defaults.type == 'powerpoint' ){
  215 + //console.log($(this).html());
  216 + var excel="<table>";
  217 + // Header
  218 + $(el).find('thead').find('tr').each(function() {
  219 + excel += "<tr>";
  220 + $(this).filter(':visible').find('th').each(function(index,data) {
  221 + if ($(this).css('display') != 'none'){
  222 + if(defaults.ignoreColumn.indexOf(index) == -1){
  223 + excel += "<td>" + parseString($(this))+ "</td>";
  224 + }
  225 + }
  226 + });
  227 + excel += '</tr>';
  228 +
  229 + });
  230 +
  231 +
  232 + // Row Vs Column
  233 + var rowCount=1;
  234 + $(el).find('tbody').find('tr').each(function() {
  235 + excel += "<tr>";
  236 + var colCount=0;
  237 + $(this).filter(':visible').find('td').each(function(index,data) {
  238 + if ($(this).css('display') != 'none'){
  239 + if(defaults.ignoreColumn.indexOf(index) == -1){
  240 + excel += "<td>"+parseString($(this))+"</td>";
  241 + }
  242 + }
  243 + colCount++;
  244 + });
  245 + rowCount++;
  246 + excel += '</tr>';
  247 + });
  248 + excel += '</table>'
  249 +
  250 + if(defaults.consoleLog == 'true'){
  251 + console.log(excel);
  252 + }
  253 +
  254 + var excelFile = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:"+defaults.type+"' xmlns='http://www.w3.org/TR/REC-html40'>";
  255 + excelFile += "<head>";
  256 + excelFile += "<!--[if gte mso 9]>";
  257 + excelFile += "<xml>";
  258 + excelFile += "<x:ExcelWorkbook>";
  259 + excelFile += "<x:ExcelWorksheets>";
  260 + excelFile += "<x:ExcelWorksheet>";
  261 + excelFile += "<x:Name>";
  262 + excelFile += "{worksheet}";
  263 + excelFile += "</x:Name>";
  264 + excelFile += "<x:WorksheetOptions>";
  265 + excelFile += "<x:DisplayGridlines/>";
  266 + excelFile += "</x:WorksheetOptions>";
  267 + excelFile += "</x:ExcelWorksheet>";
  268 + excelFile += "</x:ExcelWorksheets>";
  269 + excelFile += "</x:ExcelWorkbook>";
  270 + excelFile += "</xml>";
  271 + excelFile += "<![endif]-->";
  272 + excelFile += "</head>";
  273 + excelFile += "<body>";
  274 + excelFile += excel;
  275 + excelFile += "</body>";
  276 + excelFile += "</html>";
  277 +
  278 + var base64data = "base64," + $.base64.encode(excelFile);
  279 + window.open('data:application/vnd.ms-'+defaults.type+';filename=exportData.doc;' + base64data);
  280 +
  281 + }else if(defaults.type == 'png'){
  282 + html2canvas($(el), {
  283 + onrendered: function(canvas) {
  284 + var img = canvas.toDataURL("image/png");
  285 + window.open(img);
  286 +
  287 +
  288 + }
  289 + });
  290 + }else if(defaults.type == 'pdf'){
  291 +
  292 + var doc = new jsPDF('p','pt', 'a4', true);
  293 + doc.setFontSize(defaults.pdfFontSize);
  294 +
  295 + // Header
  296 + var startColPosition=defaults.pdfLeftMargin;
  297 + $(el).find('thead').find('tr').each(function() {
  298 + $(this).filter(':visible').find('th').each(function(index,data) {
  299 + if ($(this).css('display') != 'none'){
  300 + if(defaults.ignoreColumn.indexOf(index) == -1){
  301 + var colPosition = startColPosition+ (index * 50);
  302 + doc.text(colPosition,20, parseString($(this)));
  303 + }
  304 + }
  305 + });
  306 + });
  307 +
  308 +
  309 + // Row Vs Column
  310 + var startRowPosition = 20; var page =1;var rowPosition=0;
  311 + $(el).find('tbody').find('tr').each(function(index,data) {
  312 + rowCalc = index+1;
  313 +
  314 + if (rowCalc % 26 == 0){
  315 + doc.addPage();
  316 + page++;
  317 + startRowPosition=startRowPosition+10;
  318 + }
  319 + rowPosition=(startRowPosition + (rowCalc * 10)) - ((page -1) * 280);
  320 +
  321 + $(this).filter(':visible').find('td').each(function(index,data) {
  322 + if ($(this).css('display') != 'none'){
  323 + if(defaults.ignoreColumn.indexOf(index) == -1){
  324 + var colPosition = startColPosition+ (index * 50);
  325 + doc.text(colPosition,rowPosition, parseString($(this)));
  326 + }
  327 + }
  328 +
  329 + });
  330 +
  331 + });
  332 +
  333 + // Output as Data URI
  334 + doc.output('datauri');
  335 +
  336 + }
  337 +
  338 +
  339 + function parseString(data){
  340 +
  341 + if(defaults.htmlContent == 'true'){
  342 + content_data = data.html().trim();
  343 + }else{
  344 + content_data = data.text().trim();
  345 + }
  346 +
  347 + if(defaults.escape == 'true'){
  348 + content_data = escape(content_data);
  349 + }
  350 +
  351 +
  352 +
  353 + return content_data;
  354 + }
  355 +
  356 + }
  357 + });
  358 + })(jQuery);
  359 +
... ...