[
  {
    "path": "README.md",
    "content": "**html2canvas html截图插件图片放大清晰度解决方案，支持任意放大倍数，解决原插件图片偏移问题**\n----------------------------------------------------\n\nAuthor: Alias You (2016.12.6)\n插件下载地址：https://github.com/niklasvh/html2canvas\n\n1.首先引入html2canvas.js html2canvas 0.5.0-beta4 最新版即可\n\n必要步骤1：修改插件的源码： （修改的地方有两处）\n\n#### 1. 代码第 999 行 renderWindow 的方法中 修改判断条件 增加一个options.scale存在的条件：\n\n\n源码：\n\n```\nif (options.type === \"view\") {\n                canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});\n            } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {\n                canvas = renderer.canvas;\n            } else {\n                canvas = crop(renderer.canvas, {width:  options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});\n\n            }\n\n```\n改为：\n\n```\nif (options.type === \"view\") {\n                canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});\n            } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement) {\n                canvas = renderer.canvas;\n            }else if(options.scale && options.canvas !=null){\n                log(\"放大canvas\",options.canvas);\n                var scale = options.scale || 1;\n                canvas = crop(renderer.canvas, {width: bounds.width * scale, height:bounds.height * scale, top: bounds.top *scale, left: bounds.left *scale, x: 0, y: 0});\n            }\n            else {\n                canvas = crop(renderer.canvas, {width:  options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});\n            }\n```\n#### 2. 代码第 943 行 html2canvas 的方法中  修改width,height：\n\n\n源码：\n```\nreturn renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {\n    if (typeof(options.onrendered) === \"function\") {\n        log(\"options.onrendered is deprecated, html2canvas returns a Promise containing the canvas\");\n        options.onrendered(canvas);\n    }\n    return canvas;\n});\n```\n改为：\n\n```\nwidth = options.width != null ? options.width : node.ownerDocument.defaultView.innerWidth;\nheight = options.height != null ? options.height : node.ownerDocument.defaultView.innerHeight;\nreturn renderDocument(node.ownerDocument, options, width, height, index).then(function(canvas) {\n    if (typeof(options.onrendered) === \"function\") {\n        log(\"options.onrendered is deprecated, html2canvas returns a Promise containing the canvas\");\n        options.onrendered(canvas);\n    }\n    return canvas;\n});\n```\n\n\n\n2.使用方式\n\n```\nvar shareContent = document.getElementById(\"shareContent\");//需要截图的包裹的（原生的）DOM 对象\nvar width = shareContent.offsetWidth; //获取dom 宽度\nvar height = shareContent.offsetHeight; //获取dom 高度\nvar canvas = document.createElement(\"canvas\"); //创建一个canvas节点\nvar scale = 2; //定义任意放大倍数 支持小数\ncanvas.width = width * scale; //定义canvas 宽度 * 缩放\ncanvas.height = height * scale; //定义canvas高度 *缩放\ncanvas.getContext(\"2d\").scale(scale,scale); //获取context,设置scale \nvar opts = {\n    scale:scale, // 添加的scale 参数\n    canvas:canvas, //自定义 canvas\n    logging: true, //日志开关\n    width:width, //dom 原始宽度\n    height:height //dom 原始高度\n};\n\nhtml2canvas(shareContent, opts).then(function (canvas) {\n    //如果想要生成图片 引入canvas2Image.js 下载地址：\n    //https://github.com/hongru/canvas2image/blob/master/canvas2image.js\n    var img = Canvas2Image.convertToImage(canvas, canvas.width, canvas.height);\n    console.log(img);\n});\n```\n\n**2017.1.7 优化插件使用的方式，并附上demo （插件的改动还是按照上面的操作流程）**\n-------------------------------------------------\n\n（修复插件的使用方式上存在截图不完整的bug）\n\n\n以下我总结了一些注意事项，在代码中注释了，仅供参考。\n付：完整使用的demo ,如下：\n\n    <!DOCTYPE html>\n    <html lang=\"en\">\n    <head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\"\n      content=\"width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no\">\n    <meta http-equiv=\"pragma\" content=\"no-cache\">\n    <meta http-equiv=\"cache-control\" content=\"no-cache\">\n    <meta http-equiv=\"expires\" content=\"0\">\n    <title>html2Canvas demo</title>\n    <script>\n    document.documentElement.style.fontSize = window.screen.width / 7.5 + 'px';\n    </script>\n    <style>\n    body,\n    html,\n    div,\n    p,\n    ul,\n    li,\n    a,\n    img,\n    span,\n    button,\n    header,\n    footer,\n    section {\n    padding: 0;\n    margin: 0;\n    }\n    \n    *, :before, :after {\n    -webkit-tap-highlight-color: transparent;\n    -webkit-user-select: none;\n    outline: none;\n    box-sizing: border-box;\n    -webkit-box-sizing: border-box;\n    }\n    \n    ::-webkit-scrollbar {\n    width: 0;\n    opacity: 0;\n    }\n    \n    button{\n    font-family: simsun,\"microsoft yahei\", arial, \"Helvetica Neue\", Helvetica, STHeiTi, sans-serif;\n    }\n    body {\n    font-family: \"microsoft yahei\", arial, \"Helvetica Neue\", Helvetica, STHeiTi, sans-serif;\n    color: #000;\n    background-color: #f5f5f5;\n    -webkit-overflow-scrolling: touch;\n    }\n    .share-container {\n    padding-top: 0.72rem;\n    width: 2.35rem;\n    margin: 0 auto;\n    }\n    \n    .share-content {\n    padding-top: 0.72rem;\n    height:3rem;\n    background-color: blue;\n    border-radius: 5px;\n    width: 100%;\n    }\n    .text{\n    font-size: 0.36rem;\n    color: #f2f2f2;\n    }\n    .btn-share {\n    width: 64%;\n    height: 0.89rem;\n    background-color: #3baaff;\n    border-radius: 0.89rem;\n    border: 1px solid #3baaff;\n    color: white;\n    font-size: 0.36rem;\n    margin: 0.75rem 0 0.67rem;\n    }\n    .btn-share:active{\n    background-color: #1b96c8;\n    }\n    </style>\n    </head>\n    <body>\n    <section class=\"main-container\">\n    <header class=\"share-container\" id=\"shareContainer\">\n    <div class=\"share-content\" id=\"shareContent\">\n      <div class=\"text\">\n      <p>文字，图片等内容</p>\n      </div>\n    </div>\n    </header>\n    <footer class=\"footer-center\">\n    <button class=\"btn-share\" id=\"btnShare\">截&nbsp;图</button>\n    </footer>\n    </section>\n    \n    <script src=\"static/js/html2canvas.js\"></script>\n    <script>\n    \n    //定义查找元素方法\n    function $(selector) {\n    return document.querySelector(selector);\n    }\n    var main = {\n    init:function(){\n    main.setListener();\n    },\n    //设置监听事件\n    setListener:function(){\n    var btnShare = document.getElementById(\"btnShare\");\n    btnShare.onclick = function(){\n    main.html2Canvas();\n    }\n    },\n    //获取像素密度\n    getPixelRatio:function(context){\n    var backingStore = context.backingStorePixelRatio ||\n    context.webkitBackingStorePixelRatio ||\n    context.mozBackingStorePixelRatio ||\n    context.msBackingStorePixelRatio ||\n    context.oBackingStorePixelRatio ||\n    context.backingStorePixelRatio || 1;\n    return (window.devicePixelRatio || 1) / backingStore;\n    },\n    //绘制dom 元素，生成截图canvas\n    html2Canvas: function () {\n    var shareContent = $(\"#shareContent\");// 需要绘制的部分的 (原生）dom 对象 ，注意容器的宽度不要使用百分比，使用固定宽度，避免缩放问题\n    var width = shareContent.offsetWidth;  // 获取(原生）dom 宽度\n    var height = shareContent.offsetHeight; // 获取(原生）dom 高\n    var offsetTop = shareContent.offsetTop;  //元素距离顶部的偏移量\n    \n    var canvas = document.createElement('canvas');  //创建canvas 对象\n    var context = canvas.getContext('2d');\n    var scaleBy = main.getPixelRatio(context);  //获取像素密度的方法 (也可以采用自定义缩放比例)\n    canvas.width = width * scaleBy;   //这里 由于绘制的dom 为固定宽度，居中，所以没有偏移\n    canvas.height = (height + offsetTop) * scaleBy;  // 注意高度问题，由于顶部有个距离所以要加上顶部的距离，解决图像高度偏移问题\n    context.scale(scaleBy, scaleBy);\n    \n    var opts = {\n    allowTaint:true,//允许加载跨域的图片\n    tainttest:true, //检测每张图片都已经加载完成\n    scale:scaleBy, // 添加的scale 参数\n    canvas:canvas, //自定义 canvas\n    logging: true, //日志开关，发布的时候记得改成false\n    width:width, //dom 原始宽度\n    height:height //dom 原始高度\n    };\n    html2canvas(shareContent, opts).then(function (canvas) {\n       console.log(\"html2canvas\");\n    var body = document.getElementsByTagName(\"body\");\n    body[0].appendChild(canvas);\n    });\n    }\n    };\n    \n    //最后运行代码\n    main.init();\n    \n    </script>\n    </body>\n    </html>\n\n## 运行上面的demo 前有以下 **注意点**：\n\n1. 前面的内容没看过,没下载过html2canvas.js 没按照插件改过说明操作的先改好再说\n2. 注意元素的样式的使用：\n   外层元素width 不能使用百分比 ,避免导致图片与文字间缩放比例问题\n    错误使用方式如  \n     .container {\n           width:50%;\n           margin: 0 auto;\n     } \n\n 需要改成如：\n\n     .container {\n           width:300px;\n           margin: 0 auto;\n     } \n\n有疑问请在下方留言，可以在群里讨论和交流 QQ群：172270493，欢迎加入哦"
  },
  {
    "path": "demo.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\"\n          content=\"width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no\">\n    <meta http-equiv=\"pragma\" content=\"no-cache\">\n    <meta http-equiv=\"cache-control\" content=\"no-cache\">\n    <meta http-equiv=\"expires\" content=\"0\">\n    <title>html2Canvas demo</title>\n    <script>\n        document.documentElement.style.fontSize = window.screen.width / 7.5 + 'px';\n    </script>\n    <style>\n        body,\n        html,\n        div,\n        p,\n        ul,\n        li,\n        a,\n        img,\n        span,\n        button,\n        header,\n        footer,\n        section {\n            padding: 0;\n            margin: 0;\n        }\n\n        *, :before, :after {\n            -webkit-tap-highlight-color: transparent;\n            -webkit-user-select: none;\n            outline: none;\n            box-sizing: border-box;\n            -webkit-box-sizing: border-box;\n        }\n\n        ::-webkit-scrollbar {\n            width: 0;\n            opacity: 0;\n        }\n\n        button{\n            font-family: simsun,\"microsoft yahei\", arial, \"Helvetica Neue\", Helvetica, STHeiTi, sans-serif;\n        }\n        body {\n            font-family: \"microsoft yahei\", arial, \"Helvetica Neue\", Helvetica, STHeiTi, sans-serif;\n            color: #000;\n            background-color: #f5f5f5;\n            -webkit-overflow-scrolling: touch;\n        }\n        .share-container {\n            padding-top: 0.72rem;\n            width: 2.35rem;\n            margin: 0 auto;\n        }\n\n        .share-content {\n            padding-top: 0.72rem;\n            height:3rem;\n            background-color: blue;\n            border-radius: 5px;\n            width: 100%;\n        }\n        .text{\n            font-size: 0.36rem;\n            color: #f2f2f2;\n        }\n        .btn-share {\n            width: 64%;\n            height: 0.89rem;\n            background-color: #3baaff;\n            border-radius: 0.89rem;\n            border: 1px solid #3baaff;\n            color: white;\n            font-size: 0.36rem;\n            margin: 0.75rem 0 0.67rem;\n        }\n        .btn-share:active{\n            background-color: #1b96c8;\n        }\n    </style>\n</head>\n<body>\n<section class=\"main-container\">\n    <header class=\"share-container\" id=\"shareContainer\">\n        <div class=\"share-content\" id=\"shareContent\">\n              <div class=\"text\">\n                  <p>文字，图片等内容</p>\n              </div>\n        </div>\n    </header>\n    <footer class=\"footer-center\">\n        <button class=\"btn-share\" id=\"btnShare\">截&nbsp;图</button>\n    </footer>\n</section>\n\n<script src=\"static/js/html2canvas.js\"></script>\n<script>\n\n    //定义查找元素方法\n    function $(selector) {\n        return document.querySelector(selector);\n    }\n    var main = {\n        init:function(){\n            main.setListener();\n        },\n        //设置监听事件\n        setListener:function(){\n            var btnShare = document.getElementById(\"btnShare\");\n            btnShare.onclick = function(){\n                main.html2Canvas();\n            }\n        },\n        //获取像素密度\n        getPixelRatio:function(context){\n            var backingStore = context.backingStorePixelRatio ||\n                    context.webkitBackingStorePixelRatio ||\n                    context.mozBackingStorePixelRatio ||\n                    context.msBackingStorePixelRatio ||\n                    context.oBackingStorePixelRatio ||\n                    context.backingStorePixelRatio || 1;\n            return (window.devicePixelRatio || 1) / backingStore;\n        },\n        //绘制dom 元素，生成截图canvas\n        html2Canvas: function () {\n            var shareContent = $(\"#shareContent\");// 需要绘制的部分的 (原生）dom 对象 ，注意容器的宽度不要使用百分比，使用固定宽度，避免缩放问题\n            var width = shareContent.offsetWidth;  // 获取(原生）dom 宽度\n            var height = shareContent.offsetHeight; // 获取(原生）dom 高\n            var offsetTop = shareContent.offsetTop;  //元素距离顶部的偏移量\n\n            var canvas = document.createElement('canvas');  //创建canvas 对象\n            var context = canvas.getContext('2d');\n            var scaleBy = main.getPixelRatio(context);  //获取像素密度的方法 (也可以采用自定义缩放比例)\n            canvas.width = width * scaleBy;   //这里 由于绘制的dom 为固定宽度，居中，所以没有偏移\n            canvas.height = (height + offsetTop) * scaleBy;  // 注意高度问题，由于顶部有个距离所以要加上顶部的距离，解决图像高度偏移问题\n            context.scale(scaleBy, scaleBy);\n\n            var opts = {\n                allowTaint:true,//允许加载跨域的图片\n                tainttest:true, //检测每张图片都已经加载完成\n                scale:scaleBy, // 添加的scale 参数\n                canvas:canvas, //自定义 canvas\n                logging: true, //日志开关，发布的时候记得改成false\n                width:width, //dom 原始宽度\n                height:height //dom 原始高度\n            };\n            html2canvas(shareContent, opts).then(function (canvas) {\n               console.log(\"html2canvas\");\n                var body = document.getElementsByTagName(\"body\");\n                body[0].appendChild(canvas);\n            });\n        }\n    };\n\n    //最后运行代码\n    main.init();\n\n</script>\n</body>\n</html>"
  },
  {
    "path": "static/js/canvas2image.js",
    "content": "/**\n * covert canvas to image\n * and save the image file\n */\n\nvar Canvas2Image = function () {\n\n  // check if support sth.\n  var $support = function () {\n    var canvas = document.createElement('canvas'),\n        ctx = canvas.getContext('2d');\n\n    return {\n      canvas: !!ctx,\n      imageData: !!ctx.getImageData,\n      dataURL: !!canvas.toDataURL,\n      btoa: !!window.btoa\n    };\n  }();\n\n  var downloadMime = 'image/octet-stream';\n\n  function scaleCanvas (canvas, width, height) {\n    var w = canvas.width,\n        h = canvas.height;\n    if (width == undefined) {\n      width = w;\n    }\n    if (height == undefined) {\n      height = h;\n    }\n\n    var retCanvas = document.createElement('canvas');\n    var retCtx = retCanvas.getContext('2d');\n    retCanvas.width = width;\n    retCanvas.height = height;\n    retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);\n    return retCanvas;\n  }\n\n  function getDataURL (canvas, type, width, height) {\n    canvas = scaleCanvas(canvas, width, height);\n    return canvas.toDataURL(type);\n  }\n\n  function saveFile (strData) {\n    document.location.href = strData;\n  }\n\n  function genImage(strData) {\n    var img = document.createElement('img');\n    img.src = strData;\n    return img;\n  }\n  function fixType (type) {\n    type = type.toLowerCase().replace(/jpg/i, 'jpeg');\n    var r = type.match(/png|jpeg|bmp|gif/)[0];\n    return 'image/' + r;\n  }\n  function encodeData (data) {\n    if (!window.btoa) { throw 'btoa undefined' }\n    var str = '';\n    if (typeof data == 'string') {\n      str = data;\n    } else {\n      for (var i = 0; i < data.length; i ++) {\n        str += String.fromCharCode(data[i]);\n      }\n    }\n\n    return btoa(str);\n  }\n  function getImageData (canvas) {\n    var w = canvas.width,\n        h = canvas.height;\n    return canvas.getContext('2d').getImageData(0, 0, w, h);\n  }\n  function makeURI (strData, type) {\n    return 'data:' + type + ';base64,' + strData;\n  }\n\n\n  /**\n   * create bitmap image\n   * 按照规则生成图片响应头和响应体\n   */\n  var genBitmapImage = function (oData) {\n\n    //\n    // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx\n    // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx\n    //\n\n    var biWidth  = oData.width;\n    var biHeight\t= oData.height;\n    var biSizeImage = biWidth * biHeight * 3;\n    var bfSize  = biSizeImage + 54; // total header size = 54 bytes\n\n    //\n    //  typedef struct tagBITMAPFILEHEADER {\n    //  \tWORD bfType;\n    //  \tDWORD bfSize;\n    //  \tWORD bfReserved1;\n    //  \tWORD bfReserved2;\n    //  \tDWORD bfOffBits;\n    //  } BITMAPFILEHEADER;\n    //\n    var BITMAPFILEHEADER = [\n      // WORD bfType -- The file type signature; must be \"BM\"\n      0x42, 0x4D,\n      // DWORD bfSize -- The size, in bytes, of the bitmap file\n      bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,\n      // WORD bfReserved1 -- Reserved; must be zero\n      0, 0,\n      // WORD bfReserved2 -- Reserved; must be zero\n      0, 0,\n      // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.\n      54, 0, 0, 0\n    ];\n\n    //\n    //  typedef struct tagBITMAPINFOHEADER {\n    //  \tDWORD biSize;\n    //  \tLONG  biWidth;\n    //  \tLONG  biHeight;\n    //  \tWORD  biPlanes;\n    //  \tWORD  biBitCount;\n    //  \tDWORD biCompression;\n    //  \tDWORD biSizeImage;\n    //  \tLONG  biXPelsPerMeter;\n    //  \tLONG  biYPelsPerMeter;\n    //  \tDWORD biClrUsed;\n    //  \tDWORD biClrImportant;\n    //  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;\n    //\n    var BITMAPINFOHEADER = [\n      // DWORD biSize -- The number of bytes required by the structure\n      40, 0, 0, 0,\n      // LONG biWidth -- The width of the bitmap, in pixels\n      biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,\n      // LONG biHeight -- The height of the bitmap, in pixels\n      biHeight & 0xff, biHeight >> 8  & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,\n      // WORD biPlanes -- The number of planes for the target device. This value must be set to 1\n      1, 0,\n      // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap\n      // has a maximum of 2^24 colors (16777216, Truecolor)\n      24, 0,\n      // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed\n      0, 0, 0, 0,\n      // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps\n      biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,\n      // LONG biXPelsPerMeter, unused\n      0,0,0,0,\n      // LONG biYPelsPerMeter, unused\n      0,0,0,0,\n      // DWORD biClrUsed, the number of color indexes of palette, unused\n      0,0,0,0,\n      // DWORD biClrImportant, unused\n      0,0,0,0\n    ];\n\n    var iPadding = (4 - ((biWidth * 3) % 4)) % 4;\n\n    var aImgData = oData.data;\n\n    var strPixelData = '';\n    var biWidth4 = biWidth<<2;\n    var y = biHeight;\n    var fromCharCode = String.fromCharCode;\n\n    do {\n      var iOffsetY = biWidth4*(y-1);\n      var strPixelRow = '';\n      for (var x = 0; x < biWidth; x++) {\n        var iOffsetX = x<<2;\n        strPixelRow += fromCharCode(aImgData[iOffsetY+iOffsetX+2]) +\n            fromCharCode(aImgData[iOffsetY+iOffsetX+1]) +\n            fromCharCode(aImgData[iOffsetY+iOffsetX]);\n      }\n\n      for (var c = 0; c < iPadding; c++) {\n        strPixelRow += String.fromCharCode(0);\n      }\n\n      strPixelData += strPixelRow;\n    } while (--y);\n\n    var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);\n\n    return strEncoded;\n  };\n\n  /**\n   * saveAsImage\n   * @param canvasElement\n   * @param {String} image type\n   * @param {Number} [optional] png width\n   * @param {Number} [optional] png height\n   */\n  var saveAsImage = function (canvas, width, height, type) {\n    if ($support.canvas && $support.dataURL) {\n      if (typeof canvas == \"string\") { canvas = document.getElementById(canvas); }\n      if (type == undefined) { type = 'png'; }\n      type = fixType(type);\n      if (/bmp/.test(type)) {\n        var data = getImageData(scaleCanvas(canvas, width, height));\n        var strData = genBitmapImage(data);\n        saveFile(makeURI(strData, downloadMime));\n      } else {\n        var strData = getDataURL(canvas, type, width, height);\n        saveFile(strData.replace(type, downloadMime));\n      }\n    }\n  };\n\n  var getBase64Data = function(canvas, width, height, type){\n    if ($support.canvas && $support.dataURL) {\n      if (typeof canvas == \"string\") { canvas = document.getElementById(canvas); }\n      if (type == undefined) { type = 'png'; }\n      type = fixType(type);\n      var strData;\n      if (/bmp/.test(type)) {\n        var data = getImageData(scaleCanvas(canvas, width, height));\n        strData= genBitmapImage(data);\n      } else {\n        strData = getDataURL(canvas, type, width, height);\n      }\n      var base64;\n      var dataArray = strData.split(\"base64,\");\n      if(dataArray && dataArray.length > 1){\n        base64 = dataArray[1];\n      }\n      return base64;\n    }\n  };\n\n  var convertToImage = function (canvas, width, height, type) {\n    if ($support.canvas && $support.dataURL) {\n      if (typeof canvas == \"string\") { canvas = document.getElementById(canvas); }\n      if (type == undefined) { type = 'png'; }\n      type = fixType(type);\n\n      if (/bmp/.test(type)) {\n        var data = getImageData(scaleCanvas(canvas, width, height));\n        var strData = genBitmapImage(data);\n        return genImage(makeURI(strData, 'image/bmp'));\n      } else {\n        var strData = getDataURL(canvas, type, width, height);\n        return genImage(strData);\n      }\n    }\n  };\n\n\n\n  return {\n    getBase64Data:getBase64Data,\n    saveAsImage: saveAsImage,\n    saveAsPNG: function (canvas, width, height) {\n      return saveAsImage(canvas, width, height, 'png');\n    },\n    saveAsJPEG: function (canvas, width, height) {\n      return saveAsImage(canvas, width, height, 'jpeg');\n    },\n    saveAsGIF: function (canvas, width, height) {\n      return saveAsImage(canvas, width, height, 'gif');\n    },\n    saveAsBMP: function (canvas, width, height) {\n      return saveAsImage(canvas, width, height, 'bmp');\n    },\n\n    convertToImage: convertToImage,\n    convertToPNG: function (canvas, width, height) {\n      return convertToImage(canvas, width, height, 'png');\n    },\n    convertToJPEG: function (canvas, width, height) {\n      return convertToImage(canvas, width, height, 'jpeg');\n    },\n    convertToGIF: function (canvas, width, height) {\n      return convertToImage(canvas, width, height, 'gif');\n    },\n    convertToBMP: function (canvas, width, height) {\n      return convertToImage(canvas, width, height, 'bmp');\n    }\n  };\n\n}();"
  },
  {
    "path": "static/js/html2canvas.js",
    "content": "/*\n html2canvas 0.5.0-beta3 <http://html2canvas.hertzen.com>\n Copyright (c) 2016 Niklas von Hertzen\n\n Released under  License\n */\n\n!function(e){if(\"object\"==typeof exports&&\"undefined\"!=typeof module)module.exports=e();else if(\"function\"==typeof define&&define.amd)define([],e);else{var f;\"undefined\"!=typeof window?f=window:\"undefined\"!=typeof global?f=global:\"undefined\"!=typeof self&&(f=self),f.html2canvas=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){\n    (function (global){\n        /*! http://mths.be/punycode v1.2.4 by @mathias */\n        ;(function(root) {\n\n            /** Detect free variables */\n            var freeExports = typeof exports == 'object' && exports;\n            var freeModule = typeof module == 'object' && module &&\n                module.exports == freeExports && module;\n            var freeGlobal = typeof global == 'object' && global;\n            if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {\n                root = freeGlobal;\n            }\n\n            /**\n             * The `punycode` object.\n             * @name punycode\n             * @type Object\n             */\n            var punycode,\n\n                /** Highest positive signed 32-bit float value */\n                maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1\n\n                /** Bootstring parameters */\n                base = 36,\n                tMin = 1,\n                tMax = 26,\n                skew = 38,\n                damp = 700,\n                initialBias = 72,\n                initialN = 128, // 0x80\n                delimiter = '-', // '\\x2D'\n\n                /** Regular expressions */\n                regexPunycode = /^xn--/,\n                regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars\n                regexSeparators = /\\x2E|\\u3002|\\uFF0E|\\uFF61/g, // RFC 3490 separators\n\n                /** Error messages */\n                errors = {\n                    'overflow': 'Overflow: input needs wider integers to process',\n                    'not-basic': 'Illegal input >= 0x80 (not a basic code point)',\n                    'invalid-input': 'Invalid input'\n                },\n\n                /** Convenience shortcuts */\n                baseMinusTMin = base - tMin,\n                floor = Math.floor,\n                stringFromCharCode = String.fromCharCode,\n\n                /** Temporary variable */\n                key;\n\n            /*--------------------------------------------------------------------------*/\n\n            /**\n             * A generic error utility function.\n             * @private\n             * @param {String} type The error type.\n             * @returns {Error} Throws a `RangeError` with the applicable error message.\n             */\n            function error(type) {\n                throw RangeError(errors[type]);\n            }\n\n            /**\n             * A generic `Array#map` utility function.\n             * @private\n             * @param {Array} array The array to iterate over.\n             * @param {Function} callback The function that gets called for every array\n             * item.\n             * @returns {Array} A new array of values returned by the callback function.\n             */\n            function map(array, fn) {\n                var length = array.length;\n                while (length--) {\n                    array[length] = fn(array[length]);\n                }\n                return array;\n            }\n\n            /**\n             * A simple `Array#map`-like wrapper to work with domain name strings.\n             * @private\n             * @param {String} domain The domain name.\n             * @param {Function} callback The function that gets called for every\n             * character.\n             * @returns {Array} A new string of characters returned by the callback\n             * function.\n             */\n            function mapDomain(string, fn) {\n                return map(string.split(regexSeparators), fn).join('.');\n            }\n\n            /**\n             * Creates an array containing the numeric code points of each Unicode\n             * character in the string. While JavaScript uses UCS-2 internally,\n             * this function will convert a pair of surrogate halves (each of which\n             * UCS-2 exposes as separate characters) into a single code point,\n             * matching UTF-16.\n             * @see `punycode.ucs2.encode`\n             * @see <http://mathiasbynens.be/notes/javascript-encoding>\n             * @memberOf punycode.ucs2\n             * @name decode\n             * @param {String} string The Unicode input string (UCS-2).\n             * @returns {Array} The new array of code points.\n             */\n            function ucs2decode(string) {\n                var output = [],\n                    counter = 0,\n                    length = string.length,\n                    value,\n                    extra;\n                while (counter < length) {\n                    value = string.charCodeAt(counter++);\n                    if (value >= 0xD800 && value <= 0xDBFF && counter < length) {\n                        // high surrogate, and there is a next character\n                        extra = string.charCodeAt(counter++);\n                        if ((extra & 0xFC00) == 0xDC00) { // low surrogate\n                            output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);\n                        } else {\n                            // unmatched surrogate; only append this code unit, in case the next\n                            // code unit is the high surrogate of a surrogate pair\n                            output.push(value);\n                            counter--;\n                        }\n                    } else {\n                        output.push(value);\n                    }\n                }\n                return output;\n            }\n\n            /**\n             * Creates a string based on an array of numeric code points.\n             * @see `punycode.ucs2.decode`\n             * @memberOf punycode.ucs2\n             * @name encode\n             * @param {Array} codePoints The array of numeric code points.\n             * @returns {String} The new Unicode string (UCS-2).\n             */\n            function ucs2encode(array) {\n                return map(array, function(value) {\n                    var output = '';\n                    if (value > 0xFFFF) {\n                        value -= 0x10000;\n                        output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);\n                        value = 0xDC00 | value & 0x3FF;\n                    }\n                    output += stringFromCharCode(value);\n                    return output;\n                }).join('');\n            }\n\n            /**\n             * Converts a basic code point into a digit/integer.\n             * @see `digitToBasic()`\n             * @private\n             * @param {Number} codePoint The basic numeric code point value.\n             * @returns {Number} The numeric value of a basic code point (for use in\n             * representing integers) in the range `0` to `base - 1`, or `base` if\n             * the code point does not represent a value.\n             */\n            function basicToDigit(codePoint) {\n                if (codePoint - 48 < 10) {\n                    return codePoint - 22;\n                }\n                if (codePoint - 65 < 26) {\n                    return codePoint - 65;\n                }\n                if (codePoint - 97 < 26) {\n                    return codePoint - 97;\n                }\n                return base;\n            }\n\n            /**\n             * Converts a digit/integer into a basic code point.\n             * @see `basicToDigit()`\n             * @private\n             * @param {Number} digit The numeric value of a basic code point.\n             * @returns {Number} The basic code point whose value (when used for\n             * representing integers) is `digit`, which needs to be in the range\n             * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is\n             * used; else, the lowercase form is used. The behavior is undefined\n             * if `flag` is non-zero and `digit` has no uppercase form.\n             */\n            function digitToBasic(digit, flag) {\n                //  0..25 map to ASCII a..z or A..Z\n                // 26..35 map to ASCII 0..9\n                return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);\n            }\n\n            /**\n             * Bias adaptation function as per section 3.4 of RFC 3492.\n             * http://tools.ietf.org/html/rfc3492#section-3.4\n             * @private\n             */\n            function adapt(delta, numPoints, firstTime) {\n                var k = 0;\n                delta = firstTime ? floor(delta / damp) : delta >> 1;\n                delta += floor(delta / numPoints);\n                for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {\n                    delta = floor(delta / baseMinusTMin);\n                }\n                return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));\n            }\n\n            /**\n             * Converts a Punycode string of ASCII-only symbols to a string of Unicode\n             * symbols.\n             * @memberOf punycode\n             * @param {String} input The Punycode string of ASCII-only symbols.\n             * @returns {String} The resulting string of Unicode symbols.\n             */\n            function decode(input) {\n                // Don't use UCS-2\n                var output = [],\n                    inputLength = input.length,\n                    out,\n                    i = 0,\n                    n = initialN,\n                    bias = initialBias,\n                    basic,\n                    j,\n                    index,\n                    oldi,\n                    w,\n                    k,\n                    digit,\n                    t,\n                    /** Cached calculation results */\n                    baseMinusT;\n\n                // Handle the basic code points: let `basic` be the number of input code\n                // points before the last delimiter, or `0` if there is none, then copy\n                // the first basic code points to the output.\n\n                basic = input.lastIndexOf(delimiter);\n                if (basic < 0) {\n                    basic = 0;\n                }\n\n                for (j = 0; j < basic; ++j) {\n                    // if it's not a basic code point\n                    if (input.charCodeAt(j) >= 0x80) {\n                        error('not-basic');\n                    }\n                    output.push(input.charCodeAt(j));\n                }\n\n                // Main decoding loop: start just after the last delimiter if any basic code\n                // points were copied; start at the beginning otherwise.\n\n                for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {\n\n                    // `index` is the index of the next character to be consumed.\n                    // Decode a generalized variable-length integer into `delta`,\n                    // which gets added to `i`. The overflow checking is easier\n                    // if we increase `i` as we go, then subtract off its starting\n                    // value at the end to obtain `delta`.\n                    for (oldi = i, w = 1, k = base; /* no condition */; k += base) {\n\n                        if (index >= inputLength) {\n                            error('invalid-input');\n                        }\n\n                        digit = basicToDigit(input.charCodeAt(index++));\n\n                        if (digit >= base || digit > floor((maxInt - i) / w)) {\n                            error('overflow');\n                        }\n\n                        i += digit * w;\n                        t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);\n\n                        if (digit < t) {\n                            break;\n                        }\n\n                        baseMinusT = base - t;\n                        if (w > floor(maxInt / baseMinusT)) {\n                            error('overflow');\n                        }\n\n                        w *= baseMinusT;\n\n                    }\n\n                    out = output.length + 1;\n                    bias = adapt(i - oldi, out, oldi == 0);\n\n                    // `i` was supposed to wrap around from `out` to `0`,\n                    // incrementing `n` each time, so we'll fix that now:\n                    if (floor(i / out) > maxInt - n) {\n                        error('overflow');\n                    }\n\n                    n += floor(i / out);\n                    i %= out;\n\n                    // Insert `n` at position `i` of the output\n                    output.splice(i++, 0, n);\n\n                }\n\n                return ucs2encode(output);\n            }\n\n            /**\n             * Converts a string of Unicode symbols to a Punycode string of ASCII-only\n             * symbols.\n             * @memberOf punycode\n             * @param {String} input The string of Unicode symbols.\n             * @returns {String} The resulting Punycode string of ASCII-only symbols.\n             */\n            function encode(input) {\n                var n,\n                    delta,\n                    handledCPCount,\n                    basicLength,\n                    bias,\n                    j,\n                    m,\n                    q,\n                    k,\n                    t,\n                    currentValue,\n                    output = [],\n                    /** `inputLength` will hold the number of code points in `input`. */\n                    inputLength,\n                    /** Cached calculation results */\n                    handledCPCountPlusOne,\n                    baseMinusT,\n                    qMinusT;\n\n                // Convert the input in UCS-2 to Unicode\n                input = ucs2decode(input);\n\n                // Cache the length\n                inputLength = input.length;\n\n                // Initialize the state\n                n = initialN;\n                delta = 0;\n                bias = initialBias;\n\n                // Handle the basic code points\n                for (j = 0; j < inputLength; ++j) {\n                    currentValue = input[j];\n                    if (currentValue < 0x80) {\n                        output.push(stringFromCharCode(currentValue));\n                    }\n                }\n\n                handledCPCount = basicLength = output.length;\n\n                // `handledCPCount` is the number of code points that have been handled;\n                // `basicLength` is the number of basic code points.\n\n                // Finish the basic string - if it is not empty - with a delimiter\n                if (basicLength) {\n                    output.push(delimiter);\n                }\n\n                // Main encoding loop:\n                while (handledCPCount < inputLength) {\n\n                    // All non-basic code points < n have been handled already. Find the next\n                    // larger one:\n                    for (m = maxInt, j = 0; j < inputLength; ++j) {\n                        currentValue = input[j];\n                        if (currentValue >= n && currentValue < m) {\n                            m = currentValue;\n                        }\n                    }\n\n                    // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,\n                    // but guard against overflow\n                    handledCPCountPlusOne = handledCPCount + 1;\n                    if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {\n                        error('overflow');\n                    }\n\n                    delta += (m - n) * handledCPCountPlusOne;\n                    n = m;\n\n                    for (j = 0; j < inputLength; ++j) {\n                        currentValue = input[j];\n\n                        if (currentValue < n && ++delta > maxInt) {\n                            error('overflow');\n                        }\n\n                        if (currentValue == n) {\n                            // Represent delta as a generalized variable-length integer\n                            for (q = delta, k = base; /* no condition */; k += base) {\n                                t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);\n                                if (q < t) {\n                                    break;\n                                }\n                                qMinusT = q - t;\n                                baseMinusT = base - t;\n                                output.push(\n                                    stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))\n                                );\n                                q = floor(qMinusT / baseMinusT);\n                            }\n\n                            output.push(stringFromCharCode(digitToBasic(q, 0)));\n                            bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);\n                            delta = 0;\n                            ++handledCPCount;\n                        }\n                    }\n\n                    ++delta;\n                    ++n;\n\n                }\n                return output.join('');\n            }\n\n            /**\n             * Converts a Punycode string representing a domain name to Unicode. Only the\n             * Punycoded parts of the domain name will be converted, i.e. it doesn't\n             * matter if you call it on a string that has already been converted to\n             * Unicode.\n             * @memberOf punycode\n             * @param {String} domain The Punycode domain name to convert to Unicode.\n             * @returns {String} The Unicode representation of the given Punycode\n             * string.\n             */\n            function toUnicode(domain) {\n                return mapDomain(domain, function(string) {\n                    return regexPunycode.test(string)\n                        ? decode(string.slice(4).toLowerCase())\n                        : string;\n                });\n            }\n\n            /**\n             * Converts a Unicode string representing a domain name to Punycode. Only the\n             * non-ASCII parts of the domain name will be converted, i.e. it doesn't\n             * matter if you call it with a domain that's already in ASCII.\n             * @memberOf punycode\n             * @param {String} domain The domain name to convert, as a Unicode string.\n             * @returns {String} The Punycode representation of the given domain name.\n             */\n            function toASCII(domain) {\n                return mapDomain(domain, function(string) {\n                    return regexNonASCII.test(string)\n                        ? 'xn--' + encode(string)\n                        : string;\n                });\n            }\n\n            /*--------------------------------------------------------------------------*/\n\n            /** Define the public API */\n            punycode = {\n                /**\n                 * A string representing the current Punycode.js version number.\n                 * @memberOf punycode\n                 * @type String\n                 */\n                'version': '1.2.4',\n                /**\n                 * An object of methods to convert from JavaScript's internal character\n                 * representation (UCS-2) to Unicode code points, and back.\n                 * @see <http://mathiasbynens.be/notes/javascript-encoding>\n                 * @memberOf punycode\n                 * @type Object\n                 */\n                'ucs2': {\n                    'decode': ucs2decode,\n                    'encode': ucs2encode\n                },\n                'decode': decode,\n                'encode': encode,\n                'toASCII': toASCII,\n                'toUnicode': toUnicode\n            };\n\n            /** Expose `punycode` */\n            // Some AMD build optimizers, like r.js, check for specific condition patterns\n            // like the following:\n            if (\n                typeof define == 'function' &&\n                typeof define.amd == 'object' &&\n                define.amd\n            ) {\n                define('punycode', function() {\n                    return punycode;\n                });\n            } else if (freeExports && !freeExports.nodeType) {\n                if (freeModule) { // in Node.js or RingoJS v0.8.0+\n                    freeModule.exports = punycode;\n                } else { // in Narwhal or RingoJS v0.7.0-\n                    for (key in punycode) {\n                        punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);\n                    }\n                }\n            } else { // in Rhino or a web browser\n                root.punycode = punycode;\n            }\n\n        }(this));\n\n    }).call(this,typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {})\n},{}],2:[function(_dereq_,module,exports){\n    var log = _dereq_('./log');\n\n    function restoreOwnerScroll(ownerDocument, x, y) {\n        if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {\n            ownerDocument.defaultView.scrollTo(x, y);\n        }\n    }\n\n    function cloneCanvasContents(canvas, clonedCanvas) {\n        try {\n            if (clonedCanvas) {\n                clonedCanvas.width = canvas.width;\n                clonedCanvas.height = canvas.height;\n                clonedCanvas.getContext(\"2d\").putImageData(canvas.getContext(\"2d\").getImageData(0, 0, canvas.width, canvas.height), 0, 0);\n            }\n        } catch(e) {\n            log(\"Unable to copy canvas content from\", canvas, e);\n        }\n    }\n\n    function cloneNode(node, javascriptEnabled) {\n        var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);\n\n        var child = node.firstChild;\n        while(child) {\n            if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {\n                clone.appendChild(cloneNode(child, javascriptEnabled));\n            }\n            child = child.nextSibling;\n        }\n\n        if (node.nodeType === 1) {\n            clone._scrollTop = node.scrollTop;\n            clone._scrollLeft = node.scrollLeft;\n            if (node.nodeName === \"CANVAS\") {\n                cloneCanvasContents(node, clone);\n            } else if (node.nodeName === \"TEXTAREA\" || node.nodeName === \"SELECT\") {\n                clone.value = node.value;\n            }\n        }\n\n        return clone;\n    }\n\n    function initNode(node) {\n        if (node.nodeType === 1) {\n            node.scrollTop = node._scrollTop;\n            node.scrollLeft = node._scrollLeft;\n\n            var child = node.firstChild;\n            while(child) {\n                initNode(child);\n                child = child.nextSibling;\n            }\n        }\n    }\n\n    module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {\n        var documentElement = cloneNode(ownerDocument.documentElement, options.javascriptEnabled);\n        var container = containerDocument.createElement(\"iframe\");\n\n        container.className = \"html2canvas-container\";\n        container.style.visibility = \"hidden\";\n        container.style.position = \"fixed\";\n        container.style.left = \"-10000px\";\n        container.style.top = \"0px\";\n        container.style.border = \"0\";\n        container.width = width;\n        container.height = height;\n        container.scrolling = \"no\"; // ios won't scroll without it\n        containerDocument.body.appendChild(container);\n\n        return new Promise(function(resolve) {\n            var documentClone = container.contentWindow.document;\n\n            /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle\n             if window url is about:blank, we can assign the url to current by writing onto the document\n             */\n            container.contentWindow.onload = container.onload = function() {\n                var interval = setInterval(function() {\n                    if (documentClone.body.childNodes.length > 0) {\n                        initNode(documentClone.documentElement);\n                        clearInterval(interval);\n                        if (options.type === \"view\") {\n                            container.contentWindow.scrollTo(x, y);\n                            if ((/(iPad|iPhone|iPod)/g).test(navigator.userAgent) && (container.contentWindow.scrollY !== y || container.contentWindow.scrollX !== x)) {\n                                documentClone.documentElement.style.top = (-y) + \"px\";\n                                documentClone.documentElement.style.left = (-x) + \"px\";\n                                documentClone.documentElement.style.position = 'absolute';\n                            }\n                        }\n                        resolve(container);\n                    }\n                }, 50);\n            };\n\n            documentClone.open();\n            documentClone.write(\"<!DOCTYPE html><html></html>\");\n            // Chrome scrolls the parent document for some reason after the write to the cloned window???\n            restoreOwnerScroll(ownerDocument, x, y);\n            documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);\n            documentClone.close();\n        });\n    };\n\n},{\"./log\":13}],3:[function(_dereq_,module,exports){\n// http://dev.w3.org/csswg/css-color/\n\n    function Color(value) {\n        this.r = 0;\n        this.g = 0;\n        this.b = 0;\n        this.a = null;\n        var result = this.fromArray(value) ||\n            this.namedColor(value) ||\n            this.rgb(value) ||\n            this.rgba(value) ||\n            this.hex6(value) ||\n            this.hex3(value);\n    }\n\n    Color.prototype.darken = function(amount) {\n        var a = 1 - amount;\n        return  new Color([\n            Math.round(this.r * a),\n            Math.round(this.g * a),\n            Math.round(this.b * a),\n            this.a\n        ]);\n    };\n\n    Color.prototype.isTransparent = function() {\n        return this.a === 0;\n    };\n\n    Color.prototype.isBlack = function() {\n        return this.r === 0 && this.g === 0 && this.b === 0;\n    };\n\n    Color.prototype.fromArray = function(array) {\n        if (Array.isArray(array)) {\n            this.r = Math.min(array[0], 255);\n            this.g = Math.min(array[1], 255);\n            this.b = Math.min(array[2], 255);\n            if (array.length > 3) {\n                this.a = array[3];\n            }\n        }\n\n        return (Array.isArray(array));\n    };\n\n    var _hex3 = /^#([a-f0-9]{3})$/i;\n\n    Color.prototype.hex3 = function(value) {\n        var match = null;\n        if ((match = value.match(_hex3)) !== null) {\n            this.r = parseInt(match[1][0] + match[1][0], 16);\n            this.g = parseInt(match[1][1] + match[1][1], 16);\n            this.b = parseInt(match[1][2] + match[1][2], 16);\n        }\n        return match !== null;\n    };\n\n    var _hex6 = /^#([a-f0-9]{6})$/i;\n\n    Color.prototype.hex6 = function(value) {\n        var match = null;\n        if ((match = value.match(_hex6)) !== null) {\n            this.r = parseInt(match[1].substring(0, 2), 16);\n            this.g = parseInt(match[1].substring(2, 4), 16);\n            this.b = parseInt(match[1].substring(4, 6), 16);\n        }\n        return match !== null;\n    };\n\n\n    var _rgb = /^rgb\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)$/;\n\n    Color.prototype.rgb = function(value) {\n        var match = null;\n        if ((match = value.match(_rgb)) !== null) {\n            this.r = Number(match[1]);\n            this.g = Number(match[2]);\n            this.b = Number(match[3]);\n        }\n        return match !== null;\n    };\n\n    var _rgba = /^rgba\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d?\\.?\\d+)\\s*\\)$/;\n\n    Color.prototype.rgba = function(value) {\n        var match = null;\n        if ((match = value.match(_rgba)) !== null) {\n            this.r = Number(match[1]);\n            this.g = Number(match[2]);\n            this.b = Number(match[3]);\n            this.a = Number(match[4]);\n        }\n        return match !== null;\n    };\n\n    Color.prototype.toString = function() {\n        return this.a !== null && this.a !== 1 ?\n        \"rgba(\" + [this.r, this.g, this.b, this.a].join(\",\") + \")\" :\n        \"rgb(\" + [this.r, this.g, this.b].join(\",\") + \")\";\n    };\n\n    Color.prototype.namedColor = function(value) {\n        value = value.toLowerCase();\n        var color = colors[value];\n        if (color) {\n            this.r = color[0];\n            this.g = color[1];\n            this.b = color[2];\n        } else if (value === \"transparent\") {\n            this.r = this.g = this.b = this.a = 0;\n            return true;\n        }\n\n        return !!color;\n    };\n\n    Color.prototype.isColor = true;\n\n// JSON.stringify([].slice.call($$('.named-color-table tr'), 1).map(function(row) { return [row.childNodes[3].textContent, row.childNodes[5].textContent.trim().split(\",\").map(Number)] }).reduce(function(data, row) {data[row[0]] = row[1]; return data}, {}))\n    var colors = {\n        \"aliceblue\": [240, 248, 255],\n        \"antiquewhite\": [250, 235, 215],\n        \"aqua\": [0, 255, 255],\n        \"aquamarine\": [127, 255, 212],\n        \"azure\": [240, 255, 255],\n        \"beige\": [245, 245, 220],\n        \"bisque\": [255, 228, 196],\n        \"black\": [0, 0, 0],\n        \"blanchedalmond\": [255, 235, 205],\n        \"blue\": [0, 0, 255],\n        \"blueviolet\": [138, 43, 226],\n        \"brown\": [165, 42, 42],\n        \"burlywood\": [222, 184, 135],\n        \"cadetblue\": [95, 158, 160],\n        \"chartreuse\": [127, 255, 0],\n        \"chocolate\": [210, 105, 30],\n        \"coral\": [255, 127, 80],\n        \"cornflowerblue\": [100, 149, 237],\n        \"cornsilk\": [255, 248, 220],\n        \"crimson\": [220, 20, 60],\n        \"cyan\": [0, 255, 255],\n        \"darkblue\": [0, 0, 139],\n        \"darkcyan\": [0, 139, 139],\n        \"darkgoldenrod\": [184, 134, 11],\n        \"darkgray\": [169, 169, 169],\n        \"darkgreen\": [0, 100, 0],\n        \"darkgrey\": [169, 169, 169],\n        \"darkkhaki\": [189, 183, 107],\n        \"darkmagenta\": [139, 0, 139],\n        \"darkolivegreen\": [85, 107, 47],\n        \"darkorange\": [255, 140, 0],\n        \"darkorchid\": [153, 50, 204],\n        \"darkred\": [139, 0, 0],\n        \"darksalmon\": [233, 150, 122],\n        \"darkseagreen\": [143, 188, 143],\n        \"darkslateblue\": [72, 61, 139],\n        \"darkslategray\": [47, 79, 79],\n        \"darkslategrey\": [47, 79, 79],\n        \"darkturquoise\": [0, 206, 209],\n        \"darkviolet\": [148, 0, 211],\n        \"deeppink\": [255, 20, 147],\n        \"deepskyblue\": [0, 191, 255],\n        \"dimgray\": [105, 105, 105],\n        \"dimgrey\": [105, 105, 105],\n        \"dodgerblue\": [30, 144, 255],\n        \"firebrick\": [178, 34, 34],\n        \"floralwhite\": [255, 250, 240],\n        \"forestgreen\": [34, 139, 34],\n        \"fuchsia\": [255, 0, 255],\n        \"gainsboro\": [220, 220, 220],\n        \"ghostwhite\": [248, 248, 255],\n        \"gold\": [255, 215, 0],\n        \"goldenrod\": [218, 165, 32],\n        \"gray\": [128, 128, 128],\n        \"green\": [0, 128, 0],\n        \"greenyellow\": [173, 255, 47],\n        \"grey\": [128, 128, 128],\n        \"honeydew\": [240, 255, 240],\n        \"hotpink\": [255, 105, 180],\n        \"indianred\": [205, 92, 92],\n        \"indigo\": [75, 0, 130],\n        \"ivory\": [255, 255, 240],\n        \"khaki\": [240, 230, 140],\n        \"lavender\": [230, 230, 250],\n        \"lavenderblush\": [255, 240, 245],\n        \"lawngreen\": [124, 252, 0],\n        \"lemonchiffon\": [255, 250, 205],\n        \"lightblue\": [173, 216, 230],\n        \"lightcoral\": [240, 128, 128],\n        \"lightcyan\": [224, 255, 255],\n        \"lightgoldenrodyellow\": [250, 250, 210],\n        \"lightgray\": [211, 211, 211],\n        \"lightgreen\": [144, 238, 144],\n        \"lightgrey\": [211, 211, 211],\n        \"lightpink\": [255, 182, 193],\n        \"lightsalmon\": [255, 160, 122],\n        \"lightseagreen\": [32, 178, 170],\n        \"lightskyblue\": [135, 206, 250],\n        \"lightslategray\": [119, 136, 153],\n        \"lightslategrey\": [119, 136, 153],\n        \"lightsteelblue\": [176, 196, 222],\n        \"lightyellow\": [255, 255, 224],\n        \"lime\": [0, 255, 0],\n        \"limegreen\": [50, 205, 50],\n        \"linen\": [250, 240, 230],\n        \"magenta\": [255, 0, 255],\n        \"maroon\": [128, 0, 0],\n        \"mediumaquamarine\": [102, 205, 170],\n        \"mediumblue\": [0, 0, 205],\n        \"mediumorchid\": [186, 85, 211],\n        \"mediumpurple\": [147, 112, 219],\n        \"mediumseagreen\": [60, 179, 113],\n        \"mediumslateblue\": [123, 104, 238],\n        \"mediumspringgreen\": [0, 250, 154],\n        \"mediumturquoise\": [72, 209, 204],\n        \"mediumvioletred\": [199, 21, 133],\n        \"midnightblue\": [25, 25, 112],\n        \"mintcream\": [245, 255, 250],\n        \"mistyrose\": [255, 228, 225],\n        \"moccasin\": [255, 228, 181],\n        \"navajowhite\": [255, 222, 173],\n        \"navy\": [0, 0, 128],\n        \"oldlace\": [253, 245, 230],\n        \"olive\": [128, 128, 0],\n        \"olivedrab\": [107, 142, 35],\n        \"orange\": [255, 165, 0],\n        \"orangered\": [255, 69, 0],\n        \"orchid\": [218, 112, 214],\n        \"palegoldenrod\": [238, 232, 170],\n        \"palegreen\": [152, 251, 152],\n        \"paleturquoise\": [175, 238, 238],\n        \"palevioletred\": [219, 112, 147],\n        \"papayawhip\": [255, 239, 213],\n        \"peachpuff\": [255, 218, 185],\n        \"peru\": [205, 133, 63],\n        \"pink\": [255, 192, 203],\n        \"plum\": [221, 160, 221],\n        \"powderblue\": [176, 224, 230],\n        \"purple\": [128, 0, 128],\n        \"rebeccapurple\": [102, 51, 153],\n        \"red\": [255, 0, 0],\n        \"rosybrown\": [188, 143, 143],\n        \"royalblue\": [65, 105, 225],\n        \"saddlebrown\": [139, 69, 19],\n        \"salmon\": [250, 128, 114],\n        \"sandybrown\": [244, 164, 96],\n        \"seagreen\": [46, 139, 87],\n        \"seashell\": [255, 245, 238],\n        \"sienna\": [160, 82, 45],\n        \"silver\": [192, 192, 192],\n        \"skyblue\": [135, 206, 235],\n        \"slateblue\": [106, 90, 205],\n        \"slategray\": [112, 128, 144],\n        \"slategrey\": [112, 128, 144],\n        \"snow\": [255, 250, 250],\n        \"springgreen\": [0, 255, 127],\n        \"steelblue\": [70, 130, 180],\n        \"tan\": [210, 180, 140],\n        \"teal\": [0, 128, 128],\n        \"thistle\": [216, 191, 216],\n        \"tomato\": [255, 99, 71],\n        \"turquoise\": [64, 224, 208],\n        \"violet\": [238, 130, 238],\n        \"wheat\": [245, 222, 179],\n        \"white\": [255, 255, 255],\n        \"whitesmoke\": [245, 245, 245],\n        \"yellow\": [255, 255, 0],\n        \"yellowgreen\": [154, 205, 50]\n    };\n\n    module.exports = Color;\n\n},{}],4:[function(_dereq_,module,exports){\n    var Support = _dereq_('./support');\n    var CanvasRenderer = _dereq_('./renderers/canvas');\n    var ImageLoader = _dereq_('./imageloader');\n    var NodeParser = _dereq_('./nodeparser');\n    var NodeContainer = _dereq_('./nodecontainer');\n    var log = _dereq_('./log');\n    var utils = _dereq_('./utils');\n    var createWindowClone = _dereq_('./clone');\n    var loadUrlDocument = _dereq_('./proxy').loadUrlDocument;\n    var getBounds = utils.getBounds;\n\n    var html2canvasNodeAttribute = \"data-html2canvas-node\";\n    var html2canvasCloneIndex = 0;\n\n    function html2canvas(nodeList, options) {\n        var index = html2canvasCloneIndex++;\n        options = options || {};\n        if (options.logging) {\n            log.options.logging = true;\n            log.options.start = Date.now();\n        }\n\n        options.async = typeof(options.async) === \"undefined\" ? true : options.async;\n        options.allowTaint = typeof(options.allowTaint) === \"undefined\" ? false : options.allowTaint;\n        options.removeContainer = typeof(options.removeContainer) === \"undefined\" ? true : options.removeContainer;\n        options.javascriptEnabled = typeof(options.javascriptEnabled) === \"undefined\" ? false : options.javascriptEnabled;\n        options.imageTimeout = typeof(options.imageTimeout) === \"undefined\" ? 10000 : options.imageTimeout;\n        options.renderer = typeof(options.renderer) === \"function\" ? options.renderer : CanvasRenderer;\n        options.strict = !!options.strict;\n\n        if (typeof(nodeList) === \"string\") {\n            if (typeof(options.proxy) !== \"string\") {\n                return Promise.reject(\"Proxy must be used when rendering url\");\n            }\n            var width = options.width != null ? options.width : window.innerWidth;\n            var height = options.height != null ? options.height : window.innerHeight;\n            return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {\n                return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);\n            });\n        }\n\n        var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];\n        node.setAttribute(html2canvasNodeAttribute + index, index);\n        width = options.width != null ? options.width : node.ownerDocument.defaultView.innerWidth;\n        height = options.height != null ? options.height : node.ownerDocument.defaultView.innerHeight;\n        return renderDocument(node.ownerDocument, options, width, height, index).then(function(canvas) {\n            if (typeof(options.onrendered) === \"function\") {\n                log(\"options.onrendered is deprecated, html2canvas returns a Promise containing the canvas\");\n                options.onrendered(canvas);\n            }\n            return canvas;\n        });\n    }\n\n    html2canvas.CanvasRenderer = CanvasRenderer;\n    html2canvas.NodeContainer = NodeContainer;\n    html2canvas.log = log;\n    html2canvas.utils = utils;\n\n    var html2canvasExport = (typeof(document) === \"undefined\" || typeof(Object.create) !== \"function\" || typeof(document.createElement(\"canvas\").getContext) !== \"function\") ? function() {\n        return Promise.reject(\"No canvas support\");\n    } : html2canvas;\n\n    module.exports = html2canvasExport;\n\n    if (typeof(define) === 'function' && define.amd) {\n        define('html2canvas', [], function() {\n            return html2canvasExport;\n        });\n    }\n\n    function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {\n        return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {\n            log(\"Document cloned\");\n\n            var attributeName = html2canvasNodeAttribute + html2canvasIndex;\n            var selector = \"[\" + attributeName + \"='\" + html2canvasIndex + \"']\";\n            document.querySelector(selector).removeAttribute(attributeName);\n            var clonedWindow = container.contentWindow;\n            var node = clonedWindow.document.querySelector(selector);\n            var oncloneHandler = (typeof(options.onclone) === \"function\") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);\n            return oncloneHandler.then(function() {\n                return renderWindow(node, container, options, windowWidth, windowHeight);\n            });\n        });\n    }\n\n    function renderWindow(node, container, options, windowWidth, windowHeight) {\n        var clonedWindow = container.contentWindow;\n        var support = new Support(clonedWindow.document);\n        var imageLoader = new ImageLoader(options, support);\n        var bounds = getBounds(node);\n        var width = options.type === \"view\" ? windowWidth : documentWidth(clonedWindow.document);\n        var height = options.type === \"view\" ? windowHeight : documentHeight(clonedWindow.document);\n        console.log()\n        var renderer = new options.renderer(width, height, imageLoader, options, node.ownerDocument);\n        var parser = new NodeParser(node, renderer, support, imageLoader, options);\n        return parser.ready.then(function() {\n            log(\"Finished rendering\");\n            var canvas;\n            if (options.type === \"view\") {\n                canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});\n            } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement) {\n                canvas = renderer.canvas;\n            }else if(options.scale && options.canvas !=null){\n                log(\"放大canvas\",options.canvas);\n                var scale = options.scale || 1;\n                console.log(bounds.top *scale)\n                canvas = crop(renderer.canvas, {width: bounds.width * scale, height:bounds.height * scale, top: bounds.top *scale, left: bounds.left *scale, x: 0, y: 0});\n                // console.log(canvas.width)\n\n            }else if (options.transform && options.transform.rotate ){\n                canvas = scaledCanvas(renderer, bounds, options.transform.rotate);\n            } \n            else {\n                canvas = crop(renderer.canvas, {width:  options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});\n            }\n\n            cleanupContainer(container, options);\n            return canvas;\n        });\n    }\n\n    function cleanupContainer(container, options) {\n        if (options.removeContainer) {\n            container.parentNode.removeChild(container);\n            log(\"Cleaned up container\");\n        }\n    }\n\n    function crop(canvas, bounds) {\n        var croppedCanvas = document.createElement(\"canvas\");\n        var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));\n        var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));\n        var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));\n        var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));\n        croppedCanvas.width = bounds.width;\n        croppedCanvas.height =  bounds.height;\n        var width = x2-x1;\n        var height = y2-y1;\n\n        log(\"Cropping canvas at:\", \"left:\", bounds.left, \"top:\", bounds.top, \"width:\", width, \"height:\", height);\n        log(\"Resulting crop with width\", bounds.width, \"and height\", bounds.height, \"with x\", x1, \"and y\", y1);\n        croppedCanvas.getContext(\"2d\").drawImage(canvas, x1, y1, width, height, bounds.x, bounds.y, width, height);\n        return croppedCanvas;\n    }\n\n    function documentWidth (doc) {\n        return Math.max(\n            Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),\n            Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),\n            Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)\n        );\n    }\n\n    function documentHeight (doc) {\n        return Math.max(\n            Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),\n            Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),\n            Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)\n        );\n    }\n\n    function absoluteUrl(url) {\n        var link = document.createElement(\"a\");\n        link.href = url;\n        link.href = link.href;\n        return link;\n    }\n\n},{\"./clone\":2,\"./imageloader\":11,\"./log\":13,\"./nodecontainer\":14,\"./nodeparser\":15,\"./proxy\":16,\"./renderers/canvas\":20,\"./support\":22,\"./utils\":26}],5:[function(_dereq_,module,exports){\n    var log = _dereq_('./log');\n    var smallImage = _dereq_('./utils').smallImage;\n\n    function DummyImageContainer(src) {\n        this.src = src;\n        log(\"DummyImageContainer for\", src);\n        if (!this.promise || !this.image) {\n            log(\"Initiating DummyImageContainer\");\n            DummyImageContainer.prototype.image = new Image();\n            var image = this.image;\n            DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) {\n                image.onload = resolve;\n                image.onerror = reject;\n                image.src = smallImage();\n                if (image.complete === true) {\n                    resolve(image);\n                }\n            });\n        }\n    }\n\n    module.exports = DummyImageContainer;\n\n},{\"./log\":13,\"./utils\":26}],6:[function(_dereq_,module,exports){\n    var smallImage = _dereq_('./utils').smallImage;\n\n    function Font(family, size) {\n        var container = document.createElement('div'),\n            img = document.createElement('img'),\n            span = document.createElement('span'),\n            sampleText = 'Hidden Text',\n            baseline,\n            middle;\n\n        container.style.visibility = \"hidden\";\n        container.style.fontFamily = family;\n        container.style.fontSize = size;\n        container.style.margin = 0;\n        container.style.padding = 0;\n\n        document.body.appendChild(container);\n\n        img.src = smallImage();\n        img.width = 1;\n        img.height = 1;\n\n        img.style.margin = 0;\n        img.style.padding = 0;\n        img.style.verticalAlign = \"baseline\";\n\n        span.style.fontFamily = family;\n        span.style.fontSize = size;\n        span.style.margin = 0;\n        span.style.padding = 0;\n\n        span.appendChild(document.createTextNode(sampleText));\n        container.appendChild(span);\n        container.appendChild(img);\n        baseline = (img.offsetTop - span.offsetTop) + 1;\n\n        container.removeChild(span);\n        container.appendChild(document.createTextNode(sampleText));\n\n        container.style.lineHeight = \"normal\";\n        img.style.verticalAlign = \"super\";\n\n        middle = (img.offsetTop-container.offsetTop) + 1;\n\n        document.body.removeChild(container);\n\n        this.baseline = baseline;\n        this.lineWidth = 1;\n        this.middle = middle;\n    }\n\n    module.exports = Font;\n\n},{\"./utils\":26}],7:[function(_dereq_,module,exports){\n    var Font = _dereq_('./font');\n\n    function FontMetrics() {\n        this.data = {};\n    }\n\n    FontMetrics.prototype.getMetrics = function(family, size) {\n        if (this.data[family + \"-\" + size] === undefined) {\n            this.data[family + \"-\" + size] = new Font(family, size);\n        }\n        return this.data[family + \"-\" + size];\n    };\n\n    module.exports = FontMetrics;\n\n},{\"./font\":6}],8:[function(_dereq_,module,exports){\n    var utils = _dereq_('./utils');\n    var getBounds = utils.getBounds;\n    var loadUrlDocument = _dereq_('./proxy').loadUrlDocument;\n\n    function FrameContainer(container, sameOrigin, options) {\n        this.image = null;\n        this.src = container;\n        var self = this;\n        var bounds = getBounds(container);\n        this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) {\n            if (container.contentWindow.document.URL === \"about:blank\" || container.contentWindow.document.documentElement == null) {\n                container.contentWindow.onload = container.onload = function() {\n                    resolve(container);\n                };\n            } else {\n                resolve(container);\n            }\n        })).then(function(container) {\n            var html2canvas = _dereq_('./core');\n            return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});\n        }).then(function(canvas) {\n            return self.image = canvas;\n        });\n    }\n\n    FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) {\n        var container = this.src;\n        return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options);\n    };\n\n    module.exports = FrameContainer;\n\n},{\"./core\":4,\"./proxy\":16,\"./utils\":26}],9:[function(_dereq_,module,exports){\n    function GradientContainer(imageData) {\n        this.src = imageData.value;\n        this.colorStops = [];\n        this.type = null;\n        this.x0 = 0.5;\n        this.y0 = 0.5;\n        this.x1 = 0.5;\n        this.y1 = 0.5;\n        this.promise = Promise.resolve(true);\n    }\n\n    GradientContainer.TYPES = {\n        LINEAR: 1,\n        RADIAL: 2\n    };\n\n// TODO: support hsl[a], negative %/length values\n// TODO: support <angle> (e.g. -?\\d{1,3}(?:\\.\\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )\n    GradientContainer.REGEXP_COLORSTOP = /^\\s*(rgba?\\(\\s*\\d{1,3},\\s*\\d{1,3},\\s*\\d{1,3}(?:,\\s*[0-9\\.]+)?\\s*\\)|[a-z]{3,20}|#[a-f0-9]{3,6})(?:\\s+(\\d{1,3}(?:\\.\\d+)?)(%|px)?)?(?:\\s|$)/i;\n\n    module.exports = GradientContainer;\n\n},{}],10:[function(_dereq_,module,exports){\n    function ImageContainer(src, cors) {\n        this.src = src;\n        this.image = new Image();\n        var self = this;\n        this.tainted = null;\n        this.promise = new Promise(function(resolve, reject) {\n            self.image.onload = resolve;\n            self.image.onerror = reject;\n            if (cors) {\n                self.image.crossOrigin = \"anonymous\";\n            }\n            self.image.src = src;\n            if (self.image.complete === true) {\n                resolve(self.image);\n            }\n        });\n    }\n\n    module.exports = ImageContainer;\n\n},{}],11:[function(_dereq_,module,exports){\n    var log = _dereq_('./log');\n    var ImageContainer = _dereq_('./imagecontainer');\n    var DummyImageContainer = _dereq_('./dummyimagecontainer');\n    var ProxyImageContainer = _dereq_('./proxyimagecontainer');\n    var FrameContainer = _dereq_('./framecontainer');\n    var SVGContainer = _dereq_('./svgcontainer');\n    var SVGNodeContainer = _dereq_('./svgnodecontainer');\n    var LinearGradientContainer = _dereq_('./lineargradientcontainer');\n    var WebkitGradientContainer = _dereq_('./webkitgradientcontainer');\n    var bind = _dereq_('./utils').bind;\n\n    function ImageLoader(options, support) {\n        this.link = null;\n        this.options = options;\n        this.support = support;\n        this.origin = this.getOrigin(window.location.href);\n    }\n\n    ImageLoader.prototype.findImages = function(nodes) {\n        var images = [];\n        nodes.reduce(function(imageNodes, container) {\n            switch(container.node.nodeName) {\n                case \"IMG\":\n                    return imageNodes.concat([{\n                        args: [container.node.src],\n                        method: \"url\"\n                    }]);\n                case \"svg\":\n                case \"IFRAME\":\n                    return imageNodes.concat([{\n                        args: [container.node],\n                        method: container.node.nodeName\n                    }]);\n            }\n            return imageNodes;\n        }, []).forEach(this.addImage(images, this.loadImage), this);\n        return images;\n    };\n\n    ImageLoader.prototype.findBackgroundImage = function(images, container) {\n        container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this);\n        return images;\n    };\n\n    ImageLoader.prototype.addImage = function(images, callback) {\n        return function(newImage) {\n            newImage.args.forEach(function(image) {\n                if (!this.imageExists(images, image)) {\n                    images.splice(0, 0, callback.call(this, newImage));\n                    log('Added image #' + (images.length), typeof(image) === \"string\" ? image.substring(0, 100) : image);\n                }\n            }, this);\n        };\n    };\n\n    ImageLoader.prototype.hasImageBackground = function(imageData) {\n        return imageData.method !== \"none\";\n    };\n\n    ImageLoader.prototype.loadImage = function(imageData) {\n        if (imageData.method === \"url\") {\n            var src = imageData.args[0];\n            if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) {\n                return new SVGContainer(src);\n            } else if (src.match(/data:image\\/.*;base64,/i)) {\n                return new ImageContainer(src.replace(/url\\(['\"]{0,}|['\"]{0,}\\)$/ig, ''), false);\n            } else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) {\n                return new ImageContainer(src, false);\n            } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {\n                return new ImageContainer(src, true);\n            } else if (this.options.proxy) {\n                return new ProxyImageContainer(src, this.options.proxy);\n            } else {\n                return new DummyImageContainer(src);\n            }\n        } else if (imageData.method === \"linear-gradient\") {\n            return new LinearGradientContainer(imageData);\n        } else if (imageData.method === \"gradient\") {\n            return new WebkitGradientContainer(imageData);\n        } else if (imageData.method === \"svg\") {\n            return new SVGNodeContainer(imageData.args[0], this.support.svg);\n        } else if (imageData.method === \"IFRAME\") {\n            return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options);\n        } else {\n            return new DummyImageContainer(imageData);\n        }\n    };\n\n    ImageLoader.prototype.isSVG = function(src) {\n        return src.substring(src.length - 3).toLowerCase() === \"svg\" || SVGContainer.prototype.isInline(src);\n    };\n\n    ImageLoader.prototype.imageExists = function(images, src) {\n        return images.some(function(image) {\n            return image.src === src;\n        });\n    };\n\n    ImageLoader.prototype.isSameOrigin = function(url) {\n        return (this.getOrigin(url) === this.origin);\n    };\n\n    ImageLoader.prototype.getOrigin = function(url) {\n        var link = this.link || (this.link = document.createElement(\"a\"));\n        link.href = url;\n        link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/\n        return link.protocol + link.hostname + link.port;\n    };\n\n    ImageLoader.prototype.getPromise = function(container) {\n        return this.timeout(container, this.options.imageTimeout)['catch'](function() {\n            var dummy = new DummyImageContainer(container.src);\n            return dummy.promise.then(function(image) {\n                container.image = image;\n            });\n        });\n    };\n\n    ImageLoader.prototype.get = function(src) {\n        var found = null;\n        return this.images.some(function(img) {\n            return (found = img).src === src;\n        }) ? found : null;\n    };\n\n    ImageLoader.prototype.fetch = function(nodes) {\n        this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));\n        this.images.forEach(function(image, index) {\n            image.promise.then(function() {\n                log(\"Succesfully loaded image #\"+ (index+1), image);\n            }, function(e) {\n                log(\"Failed loading image #\"+ (index+1), image, e);\n            });\n        });\n        this.ready = Promise.all(this.images.map(this.getPromise, this));\n        log(\"Finished searching images\");\n        return this;\n    };\n\n    ImageLoader.prototype.timeout = function(container, timeout) {\n        var timer;\n        var promise = Promise.race([container.promise, new Promise(function(res, reject) {\n            timer = setTimeout(function() {\n                log(\"Timed out loading image\", container);\n                reject(container);\n            }, timeout);\n        })]).then(function(container) {\n            clearTimeout(timer);\n            return container;\n        });\n        promise['catch'](function() {\n            clearTimeout(timer);\n        });\n        return promise;\n    };\n\n    module.exports = ImageLoader;\n\n},{\"./dummyimagecontainer\":5,\"./framecontainer\":8,\"./imagecontainer\":10,\"./lineargradientcontainer\":12,\"./log\":13,\"./proxyimagecontainer\":17,\"./svgcontainer\":23,\"./svgnodecontainer\":24,\"./utils\":26,\"./webkitgradientcontainer\":27}],12:[function(_dereq_,module,exports){\n    var GradientContainer = _dereq_('./gradientcontainer');\n    var Color = _dereq_('./color');\n\n    function LinearGradientContainer(imageData) {\n        GradientContainer.apply(this, arguments);\n        this.type = GradientContainer.TYPES.LINEAR;\n\n        var hasDirection = LinearGradientContainer.REGEXP_DIRECTION.test( imageData.args[0] ) ||\n            !GradientContainer.REGEXP_COLORSTOP.test( imageData.args[0] );\n\n        if (hasDirection) {\n            imageData.args[0].split(/\\s+/).reverse().forEach(function(position, index) {\n                switch(position) {\n                    case \"left\":\n                        this.x0 = 0;\n                        this.x1 = 1;\n                        break;\n                    case \"top\":\n                        this.y0 = 0;\n                        this.y1 = 1;\n                        break;\n                    case \"right\":\n                        this.x0 = 1;\n                        this.x1 = 0;\n                        break;\n                    case \"bottom\":\n                        this.y0 = 1;\n                        this.y1 = 0;\n                        break;\n                    case \"to\":\n                        var y0 = this.y0;\n                        var x0 = this.x0;\n                        this.y0 = this.y1;\n                        this.x0 = this.x1;\n                        this.x1 = x0;\n                        this.y1 = y0;\n                        break;\n                    case \"center\":\n                        break; // centered by default\n                    // Firefox internally converts position keywords to percentages:\n                    // http://www.w3.org/TR/2010/WD-CSS2-20101207/colors.html#propdef-background-position\n                    default: // percentage or absolute length\n                        // TODO: support absolute start point positions (e.g., use bounds to convert px to a ratio)\n                        var ratio = parseFloat(position, 10) * 1e-2;\n                        if (isNaN(ratio)) { // invalid or unhandled value\n                            break;\n                        }\n                        if (index === 0) {\n                            this.y0 = ratio;\n                            this.y1 = 1 - this.y0;\n                        } else {\n                            this.x0 = ratio;\n                            this.x1 = 1 - this.x0;\n                        }\n                        break;\n                }\n            }, this);\n        } else {\n            this.y0 = 0;\n            this.y1 = 1;\n        }\n\n        this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {\n            var colorStopMatch = colorStop.match(GradientContainer.REGEXP_COLORSTOP);\n            var value = +colorStopMatch[2];\n            var unit = value === 0 ? \"%\" : colorStopMatch[3]; // treat \"0\" as \"0%\"\n            return {\n                color: new Color(colorStopMatch[1]),\n                // TODO: support absolute stop positions (e.g., compute gradient line length & convert px to ratio)\n                stop: unit === \"%\" ? value / 100 : null\n            };\n        });\n\n        if (this.colorStops[0].stop === null) {\n            this.colorStops[0].stop = 0;\n        }\n\n        if (this.colorStops[this.colorStops.length - 1].stop === null) {\n            this.colorStops[this.colorStops.length - 1].stop = 1;\n        }\n\n        // calculates and fills-in explicit stop positions when omitted from rule\n        this.colorStops.forEach(function(colorStop, index) {\n            if (colorStop.stop === null) {\n                this.colorStops.slice(index).some(function(find, count) {\n                    if (find.stop !== null) {\n                        colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop;\n                        return true;\n                    } else {\n                        return false;\n                    }\n                }, this);\n            }\n        }, this);\n    }\n\n    LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);\n\n// TODO: support <angle> (e.g. -?\\d{1,3}(?:\\.\\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )\n    LinearGradientContainer.REGEXP_DIRECTION = /^\\s*(?:to|left|right|top|bottom|center|\\d{1,3}(?:\\.\\d+)?%?)(?:\\s|$)/i;\n\n    module.exports = LinearGradientContainer;\n\n},{\"./color\":3,\"./gradientcontainer\":9}],13:[function(_dereq_,module,exports){\n    var logger = function() {\n        if (logger.options.logging && window.console && window.console.log) {\n            Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - logger.options.start) + \"ms\", \"html2canvas:\"].concat([].slice.call(arguments, 0)));\n        }\n    };\n\n    logger.options = {logging: false};\n    module.exports = logger;\n\n},{}],14:[function(_dereq_,module,exports){\n    var Color = _dereq_('./color');\n    var utils = _dereq_('./utils');\n    var getBounds = utils.getBounds;\n    var parseBackgrounds = utils.parseBackgrounds;\n    var offsetBounds = utils.offsetBounds;\n\n    function NodeContainer(node, parent) {\n        this.node = node;\n        this.parent = parent;\n        this.stack = null;\n        this.bounds = null;\n        this.borders = null;\n        this.clip = [];\n        this.backgroundClip = [];\n        this.offsetBounds = null;\n        this.visible = null;\n        this.computedStyles = null;\n        this.colors = {};\n        this.styles = {};\n        this.backgroundImages = null;\n        this.transformData = null;\n        this.transformMatrix = null;\n        this.isPseudoElement = false;\n        this.opacity = null;\n    }\n\n    NodeContainer.prototype.cloneTo = function(stack) {\n        stack.visible = this.visible;\n        stack.borders = this.borders;\n        stack.bounds = this.bounds;\n        stack.clip = this.clip;\n        stack.backgroundClip = this.backgroundClip;\n        stack.computedStyles = this.computedStyles;\n        stack.styles = this.styles;\n        stack.backgroundImages = this.backgroundImages;\n        stack.opacity = this.opacity;\n    };\n\n    NodeContainer.prototype.getOpacity = function() {\n        return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;\n    };\n\n    NodeContainer.prototype.assignStack = function(stack) {\n        this.stack = stack;\n        stack.children.push(this);\n    };\n\n    NodeContainer.prototype.isElementVisible = function() {\n        return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (\n            this.css('display') !== \"none\" &&\n            this.css('visibility') !== \"hidden\" &&\n            !this.node.hasAttribute(\"data-html2canvas-ignore\") &&\n            (this.node.nodeName !== \"INPUT\" || this.node.getAttribute(\"type\") !== \"hidden\")\n        );\n    };\n\n    NodeContainer.prototype.css = function(attribute) {\n        if (!this.computedStyles) {\n            this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? \":before\" : \":after\") : this.computedStyle(null);\n        }\n\n        return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);\n    };\n\n    NodeContainer.prototype.prefixedCss = function(attribute) {\n        var prefixes = [\"webkit\", \"moz\", \"ms\", \"o\"];\n        var value = this.css(attribute);\n        if (value === undefined) {\n            prefixes.some(function(prefix) {\n                value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));\n                return value !== undefined;\n            }, this);\n        }\n        return value === undefined ? null : value;\n    };\n\n    NodeContainer.prototype.computedStyle = function(type) {\n        return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);\n    };\n\n    NodeContainer.prototype.cssInt = function(attribute) {\n        var value = parseInt(this.css(attribute), 10);\n        return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html\n    };\n\n    NodeContainer.prototype.color = function(attribute) {\n        return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute)));\n    };\n\n    NodeContainer.prototype.cssFloat = function(attribute) {\n        var value = parseFloat(this.css(attribute));\n        return (isNaN(value)) ? 0 : value;\n    };\n\n    NodeContainer.prototype.fontWeight = function() {\n        var weight = this.css(\"fontWeight\");\n        switch(parseInt(weight, 10)){\n            case 401:\n                weight = \"bold\";\n                break;\n            case 400:\n                weight = \"normal\";\n                break;\n        }\n        return weight;\n    };\n\n    NodeContainer.prototype.parseClip = function() {\n        var matches = this.css('clip').match(this.CLIP);\n        if (matches) {\n            return {\n                top: parseInt(matches[1], 10),\n                right: parseInt(matches[2], 10),\n                bottom: parseInt(matches[3], 10),\n                left: parseInt(matches[4], 10)\n            };\n        }\n        return null;\n    };\n\n    NodeContainer.prototype.parseBackgroundImages = function() {\n        return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css(\"backgroundImage\")));\n    };\n\n    NodeContainer.prototype.cssList = function(property, index) {\n        var value = (this.css(property) || '').split(',');\n        value = value[index || 0] || value[0] || 'auto';\n        value = value.trim().split(' ');\n        if (value.length === 1) {\n            value = [value[0], isPercentage(value[0]) ? 'auto' : value[0]];\n        }\n        return value;\n    };\n\n    NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {\n        var size = this.cssList(\"backgroundSize\", index);\n        var width, height;\n\n        if (isPercentage(size[0])) {\n            width = bounds.width * parseFloat(size[0]) / 100;\n        } else if (/contain|cover/.test(size[0])) {\n            var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;\n            return (targetRatio < currentRatio ^ size[0] === 'contain') ?  {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};\n        } else {\n            width = parseInt(size[0], 10);\n        }\n\n        if (size[0] === 'auto' && size[1] === 'auto') {\n            height = image.height;\n        } else if (size[1] === 'auto') {\n            height = width / image.width * image.height;\n        } else if (isPercentage(size[1])) {\n            height =  bounds.height * parseFloat(size[1]) / 100;\n        } else {\n            height = parseInt(size[1], 10);\n        }\n\n        if (size[0] === 'auto') {\n            width = height / image.height * image.width;\n        }\n\n        return {width: width, height: height};\n    };\n\n    NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {\n        var position = this.cssList('backgroundPosition', index);\n        var left, top;\n\n        if (isPercentage(position[0])){\n            left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);\n        } else {\n            left = parseInt(position[0], 10);\n        }\n\n        if (position[1] === 'auto') {\n            top = left / image.width * image.height;\n        } else if (isPercentage(position[1])){\n            top =  (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;\n        } else {\n            top = parseInt(position[1], 10);\n        }\n\n        if (position[0] === 'auto') {\n            left = top / image.height * image.width;\n        }\n\n        return {left: left, top: top};\n    };\n\n    NodeContainer.prototype.parseBackgroundRepeat = function(index) {\n        return this.cssList(\"backgroundRepeat\", index)[0];\n    };\n\n    NodeContainer.prototype.parseTextShadows = function() {\n        var textShadow = this.css(\"textShadow\");\n        var results = [];\n\n        if (textShadow && textShadow !== 'none') {\n            var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);\n            for (var i = 0; shadows && (i < shadows.length); i++) {\n                var s = shadows[i].match(this.TEXT_SHADOW_VALUES);\n                results.push({\n                    color: new Color(s[0]),\n                    offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,\n                    offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,\n                    blur: s[3] ? s[3].replace('px', '') : 0\n                });\n            }\n        }\n        return results;\n    };\n\n    NodeContainer.prototype.parseTransform = function() {\n        if (!this.transformData) {\n            if (this.hasTransform()) {\n                var offset = this.parseBounds();\n                var origin = this.prefixedCss(\"transformOrigin\").split(\" \").map(removePx).map(asFloat);\n                origin[0] += offset.left;\n                origin[1] += offset.top;\n                this.transformData = {\n                    origin: origin,\n                    matrix: this.parseTransformMatrix()\n                };\n            } else {\n                this.transformData = {\n                    origin: [0, 0],\n                    matrix: [1, 0, 0, 1, 0, 0]\n                };\n            }\n        }\n        return this.transformData;\n    };\n\n    NodeContainer.prototype.parseTransformMatrix = function() {\n        if (!this.transformMatrix) {\n            var transform = this.prefixedCss(\"transform\");\n            var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;\n            this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];\n        }\n        return this.transformMatrix;\n    };\n\n    NodeContainer.prototype.parseBounds = function() {\n        return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));\n    };\n\n    NodeContainer.prototype.hasTransform = function() {\n        return this.parseTransformMatrix().join(\",\") !== \"1,0,0,1,0,0\" || (this.parent && this.parent.hasTransform());\n    };\n\n    NodeContainer.prototype.getValue = function() {\n        var value = this.node.value || \"\";\n        if (this.node.tagName === \"SELECT\") {\n            value = selectionValue(this.node);\n        } else if (this.node.type === \"password\") {\n            value = Array(value.length + 1).join('\\u2022'); // jshint ignore:line\n        }\n        return value.length === 0 ? (this.node.placeholder || \"\") : value;\n    };\n\n    NodeContainer.prototype.MATRIX_PROPERTY = /(matrix|matrix3d)\\((.+)\\)/;\n    NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\\([^\\)]+\\)(\\s-?\\d+px){0,})/g;\n    NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\\d+px)|(#.+)|(rgb\\(.+\\))|(rgba\\(.+\\))/g;\n    NodeContainer.prototype.CLIP = /^rect\\((\\d+)px,? (\\d+)px,? (\\d+)px,? (\\d+)px\\)$/;\n\n    function selectionValue(node) {\n        var option = node.options[node.selectedIndex || 0];\n        return option ? (option.text || \"\") : \"\";\n    }\n\n    function parseMatrix(match) {\n        if (match && match[1] === \"matrix\") {\n            return match[2].split(\",\").map(function(s) {\n                return parseFloat(s.trim());\n            });\n        } else if (match && match[1] === \"matrix3d\") {\n            var matrix3d = match[2].split(\",\").map(function(s) {\n                return parseFloat(s.trim());\n            });\n            return [matrix3d[0], matrix3d[1], matrix3d[4], matrix3d[5], matrix3d[12], matrix3d[13]];\n        }\n    }\n\n    function isPercentage(value) {\n        return value.toString().indexOf(\"%\") !== -1;\n    }\n\n    function removePx(str) {\n        return str.replace(\"px\", \"\");\n    }\n\n    function asFloat(str) {\n        return parseFloat(str);\n    }\n\n    module.exports = NodeContainer;\n\n},{\"./color\":3,\"./utils\":26}],15:[function(_dereq_,module,exports){\n    var log = _dereq_('./log');\n    var punycode = _dereq_('punycode');\n    var NodeContainer = _dereq_('./nodecontainer');\n    var TextContainer = _dereq_('./textcontainer');\n    var PseudoElementContainer = _dereq_('./pseudoelementcontainer');\n    var FontMetrics = _dereq_('./fontmetrics');\n    var Color = _dereq_('./color');\n    var StackingContext = _dereq_('./stackingcontext');\n    var utils = _dereq_('./utils');\n    var bind = utils.bind;\n    var getBounds = utils.getBounds;\n    var parseBackgrounds = utils.parseBackgrounds;\n    var offsetBounds = utils.offsetBounds;\n\n    function NodeParser(element, renderer, support, imageLoader, options) {\n        log(\"Starting NodeParser\");\n        this.renderer = renderer;\n        this.options = options;\n        this.range = null;\n        this.support = support;\n        this.renderQueue = [];\n        this.stack = new StackingContext(true, 1, element.ownerDocument, null);\n        var parent = new NodeContainer(element, null);\n        if (options.background) {\n            renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background));\n        }\n        if (element === element.ownerDocument.documentElement) {\n            // http://www.w3.org/TR/css3-background/#special-backgrounds\n            var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null);\n            renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor'));\n        }\n        parent.visibile = parent.isElementVisible();\n        this.createPseudoHideStyles(element.ownerDocument);\n        this.disableAnimations(element.ownerDocument);\n        this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {\n            return container.visible = container.isElementVisible();\n        }).map(this.getPseudoElements, this));\n        this.fontMetrics = new FontMetrics();\n        log(\"Fetched nodes, total:\", this.nodes.length);\n        log(\"Calculate overflow clips\");\n        this.calculateOverflowClips();\n        log(\"Start fetching images\");\n        this.images = imageLoader.fetch(this.nodes.filter(isElement));\n        this.ready = this.images.ready.then(bind(function() {\n            log(\"Images loaded, starting parsing\");\n            log(\"Creating stacking contexts\");\n            this.createStackingContexts();\n            log(\"Sorting stacking contexts\");\n            this.sortStackingContexts(this.stack);\n            this.parse(this.stack);\n            log(\"Render queue created with \" + this.renderQueue.length + \" items\");\n            return new Promise(bind(function(resolve) {\n                if (!options.async) {\n                    this.renderQueue.forEach(this.paint, this);\n                    resolve();\n                } else if (typeof(options.async) === \"function\") {\n                    options.async.call(this, this.renderQueue, resolve);\n                } else if (this.renderQueue.length > 0){\n                    this.renderIndex = 0;\n                    this.asyncRenderer(this.renderQueue, resolve);\n                } else {\n                    resolve();\n                }\n            }, this));\n        }, this));\n    }\n\n    NodeParser.prototype.calculateOverflowClips = function() {\n        this.nodes.forEach(function(container) {\n            if (isElement(container)) {\n                if (isPseudoElement(container)) {\n                    container.appendToDOM();\n                }\n                container.borders = this.parseBorders(container);\n                var clip = (container.css('overflow') === \"hidden\") ? [container.borders.clip] : [];\n                var cssClip = container.parseClip();\n                if (cssClip && [\"absolute\", \"fixed\"].indexOf(container.css('position')) !== -1) {\n                    clip.push([[\"rect\",\n                        container.bounds.left + cssClip.left,\n                        container.bounds.top + cssClip.top,\n                        cssClip.right - cssClip.left,\n                        cssClip.bottom - cssClip.top\n                    ]]);\n                }\n                container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;\n                container.backgroundClip = (container.css('overflow') !== \"hidden\") ? container.clip.concat([container.borders.clip]) : container.clip;\n                if (isPseudoElement(container)) {\n                    container.cleanDOM();\n                }\n            } else if (isTextNode(container)) {\n                container.clip = hasParentClip(container) ? container.parent.clip : [];\n            }\n            if (!isPseudoElement(container)) {\n                container.bounds = null;\n            }\n        }, this);\n    };\n\n    function hasParentClip(container) {\n        return container.parent && container.parent.clip.length;\n    }\n\n    NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {\n        asyncTimer = asyncTimer || Date.now();\n        this.paint(queue[this.renderIndex++]);\n        if (queue.length === this.renderIndex) {\n            resolve();\n        } else if (asyncTimer + 20 > Date.now()) {\n            this.asyncRenderer(queue, resolve, asyncTimer);\n        } else {\n            setTimeout(bind(function() {\n                this.asyncRenderer(queue, resolve);\n            }, this), 0);\n        }\n    };\n\n    NodeParser.prototype.createPseudoHideStyles = function(document) {\n        this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: \"\" !important; display: none !important; }' +\n            '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: \"\" !important; display: none !important; }');\n    };\n\n    NodeParser.prototype.disableAnimations = function(document) {\n        this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' +\n            '-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}');\n    };\n\n    NodeParser.prototype.createStyles = function(document, styles) {\n        var hidePseudoElements = document.createElement('style');\n        hidePseudoElements.innerHTML = styles;\n        document.body.appendChild(hidePseudoElements);\n    };\n\n    NodeParser.prototype.getPseudoElements = function(container) {\n        var nodes = [[container]];\n        if (container.node.nodeType === Node.ELEMENT_NODE) {\n            var before = this.getPseudoElement(container, \":before\");\n            var after = this.getPseudoElement(container, \":after\");\n\n            if (before) {\n                nodes.push(before);\n            }\n\n            if (after) {\n                nodes.push(after);\n            }\n        }\n        return flatten(nodes);\n    };\n\n    function toCamelCase(str) {\n        return str.replace(/(\\-[a-z])/g, function(match){\n            return match.toUpperCase().replace('-','');\n        });\n    }\n\n    NodeParser.prototype.getPseudoElement = function(container, type) {\n        var style = container.computedStyle(type);\n        if(!style || !style.content || style.content === \"none\" || style.content === \"-moz-alt-content\" || style.display === \"none\") {\n            return null;\n        }\n\n        var content = stripQuotes(style.content);\n        var isImage = content.substr(0, 3) === 'url';\n        var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');\n        var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);\n\n        for (var i = style.length-1; i >= 0; i--) {\n            var property = toCamelCase(style.item(i));\n            pseudoNode.style[property] = style[property];\n        }\n\n        pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + \" \" + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;\n\n        if (isImage) {\n            pseudoNode.src = parseBackgrounds(content)[0].args[0];\n            return [pseudoContainer];\n        } else {\n            var text = document.createTextNode(content);\n            pseudoNode.appendChild(text);\n            return [pseudoContainer, new TextContainer(text, pseudoContainer)];\n        }\n    };\n\n\n    NodeParser.prototype.getChildren = function(parentContainer) {\n        return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {\n            var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);\n            return node.nodeType === Node.ELEMENT_NODE && container.length && node.tagName !== \"TEXTAREA\" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;\n        }, this));\n    };\n\n    NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {\n        var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);\n        container.cloneTo(stack);\n        var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;\n        parentStack.contexts.push(stack);\n        container.stack = stack;\n    };\n\n    NodeParser.prototype.createStackingContexts = function() {\n        this.nodes.forEach(function(container) {\n            if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {\n                this.newStackingContext(container, true);\n            } else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {\n                this.newStackingContext(container, false);\n            } else {\n                container.assignStack(container.parent.stack);\n            }\n        }, this);\n    };\n\n    NodeParser.prototype.isBodyWithTransparentRoot = function(container) {\n        return container.node.nodeName === \"BODY\" && container.parent.color('backgroundColor').isTransparent();\n    };\n\n    NodeParser.prototype.isRootElement = function(container) {\n        return container.parent === null;\n    };\n\n    NodeParser.prototype.sortStackingContexts = function(stack) {\n        stack.contexts.sort(zIndexSort(stack.contexts.slice(0)));\n        stack.contexts.forEach(this.sortStackingContexts, this);\n    };\n\n    NodeParser.prototype.parseTextBounds = function(container) {\n        return function(text, index, textList) {\n            if (container.parent.css(\"textDecoration\").substr(0, 4) !== \"none\" || text.trim().length !== 0) {\n                if (this.support.rangeBounds && !container.parent.hasTransform()) {\n                    var offset = textList.slice(0, index).join(\"\").length;\n                    return this.getRangeBounds(container.node, offset, text.length);\n                } else if (container.node && typeof(container.node.data) === \"string\") {\n                    var replacementNode = container.node.splitText(text.length);\n                    var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());\n                    container.node = replacementNode;\n                    return bounds;\n                }\n            } else if(!this.support.rangeBounds || container.parent.hasTransform()){\n                container.node = container.node.splitText(text.length);\n            }\n            return {};\n        };\n    };\n\n    NodeParser.prototype.getWrapperBounds = function(node, transform) {\n        var wrapper = node.ownerDocument.createElement('html2canvaswrapper');\n        var parent = node.parentNode,\n            backupText = node.cloneNode(true);\n\n        wrapper.appendChild(node.cloneNode(true));\n        parent.replaceChild(wrapper, node);\n        var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);\n        parent.replaceChild(backupText, wrapper);\n        return bounds;\n    };\n\n    NodeParser.prototype.getRangeBounds = function(node, offset, length) {\n        var range = this.range || (this.range = node.ownerDocument.createRange());\n        range.setStart(node, offset);\n        range.setEnd(node, offset + length);\n        return range.getBoundingClientRect();\n    };\n\n    function ClearTransform() {}\n\n    NodeParser.prototype.parse = function(stack) {\n        // http://www.w3.org/TR/CSS21/visuren.html#z-index\n        var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).\n        var descendantElements = stack.children.filter(isElement);\n        var descendantNonFloats = descendantElements.filter(not(isFloating));\n        var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.\n        var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.\n        var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.\n        var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.\n        var text = stack.children.filter(isTextNode).filter(hasText);\n        var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).\n        negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)\n            .concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {\n            this.renderQueue.push(container);\n            if (isStackingContext(container)) {\n                this.parse(container);\n                this.renderQueue.push(new ClearTransform());\n            }\n        }, this);\n    };\n\n    NodeParser.prototype.paint = function(container) {\n        try {\n            if (container instanceof ClearTransform) {\n                this.renderer.ctx.restore();\n            } else if (isTextNode(container)) {\n                if (isPseudoElement(container.parent)) {\n                    container.parent.appendToDOM();\n                }\n                this.paintText(container);\n                if (isPseudoElement(container.parent)) {\n                    container.parent.cleanDOM();\n                }\n            } else {\n                this.paintNode(container);\n            }\n        } catch(e) {\n            log(e);\n            if (this.options.strict) {\n                throw e;\n            }\n        }\n    };\n\n    NodeParser.prototype.paintNode = function(container) {\n        if (isStackingContext(container)) {\n            this.renderer.setOpacity(container.opacity);\n            this.renderer.ctx.save();\n            if (container.hasTransform()) {\n                this.renderer.setTransform(container.parseTransform());\n            }\n        }\n\n        if (container.node.nodeName === \"INPUT\" && container.node.type === \"checkbox\") {\n            this.paintCheckbox(container);\n        } else if (container.node.nodeName === \"INPUT\" && container.node.type === \"radio\") {\n            this.paintRadio(container);\n        } else {\n            this.paintElement(container);\n        }\n    };\n\n    NodeParser.prototype.paintElement = function(container) {\n        var bounds = container.parseBounds();\n        this.renderer.clip(container.backgroundClip, function() {\n            this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));\n        }, this);\n\n        this.renderer.clip(container.clip, function() {\n            this.renderer.renderBorders(container.borders.borders);\n        }, this);\n\n        this.renderer.clip(container.backgroundClip, function() {\n            switch (container.node.nodeName) {\n                case \"svg\":\n                case \"IFRAME\":\n                    var imgContainer = this.images.get(container.node);\n                    if (imgContainer) {\n                        this.renderer.renderImage(container, bounds, container.borders, imgContainer);\n                    } else {\n                        log(\"Error loading <\" + container.node.nodeName + \">\", container.node);\n                    }\n                    break;\n                case \"IMG\":\n                    var imageContainer = this.images.get(container.node.src);\n                    if (imageContainer) {\n                        this.renderer.renderImage(container, bounds, container.borders, imageContainer);\n                    } else {\n                        log(\"Error loading <img>\", container.node.src);\n                    }\n                    break;\n                case \"CANVAS\":\n                    this.renderer.renderImage(container, bounds, container.borders, {image: container.node});\n                    break;\n                case \"SELECT\":\n                case \"INPUT\":\n                case \"TEXTAREA\":\n                    this.paintFormValue(container);\n                    break;\n            }\n        }, this);\n    };\n\n    NodeParser.prototype.paintCheckbox = function(container) {\n        var b = container.parseBounds();\n\n        var size = Math.min(b.width, b.height);\n        var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left};\n        var r = [3, 3];\n        var radius = [r, r, r, r];\n        var borders = [1,1,1,1].map(function(w) {\n            return {color: new Color('#A5A5A5'), width: w};\n        });\n\n        var borderPoints = calculateCurvePoints(bounds, radius, borders);\n\n        this.renderer.clip(container.backgroundClip, function() {\n            this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color(\"#DEDEDE\"));\n            this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius));\n            if (container.node.checked) {\n                this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + \"px\", 'arial');\n                this.renderer.text(\"\\u2714\", bounds.left + size / 6, bounds.top + size - 1);\n            }\n        }, this);\n    };\n\n    NodeParser.prototype.paintRadio = function(container) {\n        var bounds = container.parseBounds();\n\n        var size = Math.min(bounds.width, bounds.height) - 2;\n\n        this.renderer.clip(container.backgroundClip, function() {\n            this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5'));\n            if (container.node.checked) {\n                this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242'));\n            }\n        }, this);\n    };\n\n    NodeParser.prototype.paintFormValue = function(container) {\n        var value = container.getValue();\n        if (value.length > 0) {\n            var document = container.node.ownerDocument;\n            var wrapper = document.createElement('html2canvaswrapper');\n            var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color',\n                'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',\n                'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth',\n                'boxSizing', 'whiteSpace', 'wordWrap'];\n\n            properties.forEach(function(property) {\n                try {\n                    wrapper.style[property] = container.css(property);\n                } catch(e) {\n                    // Older IE has issues with \"border\"\n                    log(\"html2canvas: Parse: Exception caught in renderFormValue: \" + e.message);\n                }\n            });\n            var bounds = container.parseBounds();\n            wrapper.style.position = \"fixed\";\n            wrapper.style.left = bounds.left + \"px\";\n            wrapper.style.top = bounds.top + \"px\";\n            wrapper.textContent = value;\n            document.body.appendChild(wrapper);\n            this.paintText(new TextContainer(wrapper.firstChild, container));\n            document.body.removeChild(wrapper);\n        }\n    };\n\n    NodeParser.prototype.paintText = function(container) {\n        container.applyTextTransform();\n        var characters = punycode.ucs2.decode(container.node.data);\n        var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(character) {\n            return punycode.ucs2.encode([character]);\n        });\n\n        var weight = container.parent.fontWeight();\n        var size = container.parent.css('fontSize');\n        var family = container.parent.css('fontFamily');\n        var shadows = container.parent.parseTextShadows();\n\n        this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);\n        if (shadows.length) {\n            // TODO: support multiple text shadows\n            this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur);\n        } else {\n            this.renderer.clearShadow();\n        }\n\n        this.renderer.clip(container.parent.clip, function() {\n            textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {\n                if (bounds) {\n                    this.renderer.text(textList[index], bounds.left, bounds.bottom);\n                    this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));\n                }\n            }, this);\n        }, this);\n    };\n\n    NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {\n        switch(container.css(\"textDecoration\").split(\" \")[0]) {\n            case \"underline\":\n                // Draws a line at the baseline of the font\n                // 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\n                this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.color(\"color\"));\n                break;\n            case \"overline\":\n                this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color(\"color\"));\n                break;\n            case \"line-through\":\n                // TODO try and find exact position for line-through\n                this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.color(\"color\"));\n                break;\n        }\n    };\n\n    var borderColorTransforms = {\n        inset: [\n            [\"darken\", 0.60],\n            [\"darken\", 0.10],\n            [\"darken\", 0.10],\n            [\"darken\", 0.60]\n        ]\n    };\n\n    NodeParser.prototype.parseBorders = function(container) {\n        var nodeBounds = container.parseBounds();\n        var radius = getBorderRadiusData(container);\n        var borders = [\"Top\", \"Right\", \"Bottom\", \"Left\"].map(function(side, index) {\n            var style = container.css('border' + side + 'Style');\n            var color = container.color('border' + side + 'Color');\n            if (style === \"inset\" && color.isBlack()) {\n                color = new Color([255, 255, 255, color.a]); // this is wrong, but\n            }\n            var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null;\n            return {\n                width: container.cssInt('border' + side + 'Width'),\n                color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color,\n                args: null\n            };\n        });\n        var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);\n\n        return {\n            clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),\n            borders: calculateBorders(borders, nodeBounds, borderPoints, radius)\n        };\n    };\n\n    function calculateBorders(borders, nodeBounds, borderPoints, radius) {\n        return borders.map(function(border, borderSide) {\n            if (border.width > 0) {\n                var bx = nodeBounds.left;\n                var by = nodeBounds.top;\n                var bw = nodeBounds.width;\n                var bh = nodeBounds.height - (borders[2].width);\n\n                switch(borderSide) {\n                    case 0:\n                        // top border\n                        bh = borders[0].width;\n                        border.args = drawSide({\n                                c1: [bx, by],\n                                c2: [bx + bw, by],\n                                c3: [bx + bw - borders[1].width, by + bh],\n                                c4: [bx + borders[3].width, by + bh]\n                            }, radius[0], radius[1],\n                            borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);\n                        break;\n                    case 1:\n                        // right border\n                        bx = nodeBounds.left + nodeBounds.width - (borders[1].width);\n                        bw = borders[1].width;\n\n                        border.args = drawSide({\n                                c1: [bx + bw, by],\n                                c2: [bx + bw, by + bh + borders[2].width],\n                                c3: [bx, by + bh],\n                                c4: [bx, by + borders[0].width]\n                            }, radius[1], radius[2],\n                            borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);\n                        break;\n                    case 2:\n                        // bottom border\n                        by = (by + nodeBounds.height) - (borders[2].width);\n                        bh = borders[2].width;\n                        border.args = drawSide({\n                                c1: [bx + bw, by + bh],\n                                c2: [bx, by + bh],\n                                c3: [bx + borders[3].width, by],\n                                c4: [bx + bw - borders[3].width, by]\n                            }, radius[2], radius[3],\n                            borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);\n                        break;\n                    case 3:\n                        // left border\n                        bw = borders[3].width;\n                        border.args = drawSide({\n                                c1: [bx, by + bh + borders[2].width],\n                                c2: [bx, by],\n                                c3: [bx + bw, by + borders[0].width],\n                                c4: [bx + bw, by + bh]\n                            }, radius[3], radius[0],\n                            borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);\n                        break;\n                }\n            }\n            return border;\n        });\n    }\n\n    NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {\n        var backgroundClip = container.css('backgroundClip'),\n            borderArgs = [];\n\n        switch(backgroundClip) {\n            case \"content-box\":\n            case \"padding-box\":\n                parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);\n                parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);\n                parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);\n                parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);\n                break;\n\n            default:\n                parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);\n                parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);\n                parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);\n                parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);\n                break;\n        }\n\n        return borderArgs;\n    };\n\n    function getCurvePoints(x, y, r1, r2) {\n        var kappa = 4 * ((Math.sqrt(2) - 1) / 3);\n        var ox = (r1) * kappa, // control point offset horizontal\n            oy = (r2) * kappa, // control point offset vertical\n            xm = x + r1, // x-middle\n            ym = y + r2; // y-middle\n        return {\n            topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),\n            topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),\n            bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),\n            bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})\n        };\n    }\n\n    function calculateCurvePoints(bounds, borderRadius, borders) {\n        var x = bounds.left,\n            y = bounds.top,\n            width = bounds.width,\n            height = bounds.height,\n\n            tlh = borderRadius[0][0] < width / 2 ? borderRadius[0][0] : width / 2,\n            tlv = borderRadius[0][1] < height / 2 ? borderRadius[0][1] : height / 2,\n            trh = borderRadius[1][0] < width / 2 ? borderRadius[1][0] : width / 2,\n            trv = borderRadius[1][1] < height / 2 ? borderRadius[1][1] : height / 2,\n            brh = borderRadius[2][0] < width / 2 ? borderRadius[2][0] : width / 2,\n            brv = borderRadius[2][1] < height / 2 ? borderRadius[2][1] : height / 2,\n            blh = borderRadius[3][0] < width / 2 ? borderRadius[3][0] : width / 2,\n            blv = borderRadius[3][1] < height / 2 ? borderRadius[3][1] : height / 2;\n\n        var topWidth = width - trh,\n            rightHeight = height - brv,\n            bottomWidth = width - brh,\n            leftHeight = height - blv;\n\n        return {\n            topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),\n            topLeftInner: getCurvePoints(x + borders[3].width, y + borders[0].width, Math.max(0, tlh - borders[3].width), Math.max(0, tlv - borders[0].width)).topLeft.subdivide(0.5),\n            topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),\n            topRightInner: getCurvePoints(x + Math.min(topWidth, width + borders[3].width), y + borders[0].width, (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, trv - borders[0].width).topRight.subdivide(0.5),\n            bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),\n            bottomRightInner: getCurvePoints(x + Math.min(bottomWidth, width - borders[3].width), y + Math.min(rightHeight, height + borders[0].width), Math.max(0, brh - borders[1].width),  brv - borders[2].width).bottomRight.subdivide(0.5),\n            bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),\n            bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), blv - borders[2].width).bottomLeft.subdivide(0.5)\n        };\n    }\n\n    function bezierCurve(start, startControl, endControl, end) {\n        var lerp = function (a, b, t) {\n            return {\n                x: a.x + (b.x - a.x) * t,\n                y: a.y + (b.y - a.y) * t\n            };\n        };\n\n        return {\n            start: start,\n            startControl: startControl,\n            endControl: endControl,\n            end: end,\n            subdivide: function(t) {\n                var ab = lerp(start, startControl, t),\n                    bc = lerp(startControl, endControl, t),\n                    cd = lerp(endControl, end, t),\n                    abbc = lerp(ab, bc, t),\n                    bccd = lerp(bc, cd, t),\n                    dest = lerp(abbc, bccd, t);\n                return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];\n            },\n            curveTo: function(borderArgs) {\n                borderArgs.push([\"bezierCurve\", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);\n            },\n            curveToReversed: function(borderArgs) {\n                borderArgs.push([\"bezierCurve\", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);\n            }\n        };\n    }\n\n    function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {\n        var borderArgs = [];\n\n        if (radius1[0] > 0 || radius1[1] > 0) {\n            borderArgs.push([\"line\", outer1[1].start.x, outer1[1].start.y]);\n            outer1[1].curveTo(borderArgs);\n        } else {\n            borderArgs.push([ \"line\", borderData.c1[0], borderData.c1[1]]);\n        }\n\n        if (radius2[0] > 0 || radius2[1] > 0) {\n            borderArgs.push([\"line\", outer2[0].start.x, outer2[0].start.y]);\n            outer2[0].curveTo(borderArgs);\n            borderArgs.push([\"line\", inner2[0].end.x, inner2[0].end.y]);\n            inner2[0].curveToReversed(borderArgs);\n        } else {\n            borderArgs.push([\"line\", borderData.c2[0], borderData.c2[1]]);\n            borderArgs.push([\"line\", borderData.c3[0], borderData.c3[1]]);\n        }\n\n        if (radius1[0] > 0 || radius1[1] > 0) {\n            borderArgs.push([\"line\", inner1[1].end.x, inner1[1].end.y]);\n            inner1[1].curveToReversed(borderArgs);\n        } else {\n            borderArgs.push([\"line\", borderData.c4[0], borderData.c4[1]]);\n        }\n\n        return borderArgs;\n    }\n\n    function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {\n        if (radius1[0] > 0 || radius1[1] > 0) {\n            borderArgs.push([\"line\", corner1[0].start.x, corner1[0].start.y]);\n            corner1[0].curveTo(borderArgs);\n            corner1[1].curveTo(borderArgs);\n        } else {\n            borderArgs.push([\"line\", x, y]);\n        }\n\n        if (radius2[0] > 0 || radius2[1] > 0) {\n            borderArgs.push([\"line\", corner2[0].start.x, corner2[0].start.y]);\n        }\n    }\n\n    function negativeZIndex(container) {\n        return container.cssInt(\"zIndex\") < 0;\n    }\n\n    function positiveZIndex(container) {\n        return container.cssInt(\"zIndex\") > 0;\n    }\n\n    function zIndex0(container) {\n        return container.cssInt(\"zIndex\") === 0;\n    }\n\n    function inlineLevel(container) {\n        return [\"inline\", \"inline-block\", \"inline-table\"].indexOf(container.css(\"display\")) !== -1;\n    }\n\n    function isStackingContext(container) {\n        return (container instanceof StackingContext);\n    }\n\n    function hasText(container) {\n        return container.node.data.trim().length > 0;\n    }\n\n    function noLetterSpacing(container) {\n        return (/^(normal|none|0px)$/.test(container.parent.css(\"letterSpacing\")));\n    }\n\n    function getBorderRadiusData(container) {\n        return [\"TopLeft\", \"TopRight\", \"BottomRight\", \"BottomLeft\"].map(function(side) {\n            var value = container.css('border' + side + 'Radius');\n            var arr = value.split(\" \");\n            if (arr.length <= 1) {\n                arr[1] = arr[0];\n            }\n            return arr.map(asInt);\n        });\n    }\n\n    function renderableNode(node) {\n        return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);\n    }\n\n    function isPositionedForStacking(container) {\n        var position = container.css(\"position\");\n        var zIndex = ([\"absolute\", \"relative\", \"fixed\"].indexOf(position) !== -1) ? container.css(\"zIndex\") : \"auto\";\n        return zIndex !== \"auto\";\n    }\n\n    function isPositioned(container) {\n        return container.css(\"position\") !== \"static\";\n    }\n\n    function isFloating(container) {\n        return container.css(\"float\") !== \"none\";\n    }\n\n    function isInlineBlock(container) {\n        return [\"inline-block\", \"inline-table\"].indexOf(container.css(\"display\")) !== -1;\n    }\n\n    function not(callback) {\n        var context = this;\n        return function() {\n            return !callback.apply(context, arguments);\n        };\n    }\n\n    function isElement(container) {\n        return container.node.nodeType === Node.ELEMENT_NODE;\n    }\n\n    function isPseudoElement(container) {\n        return container.isPseudoElement === true;\n    }\n\n    function isTextNode(container) {\n        return container.node.nodeType === Node.TEXT_NODE;\n    }\n\n    function zIndexSort(contexts) {\n        return function(a, b) {\n            return (a.cssInt(\"zIndex\") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt(\"zIndex\") + (contexts.indexOf(b) / contexts.length));\n        };\n    }\n\n    function hasOpacity(container) {\n        return container.getOpacity() < 1;\n    }\n\n    function asInt(value) {\n        return parseInt(value, 10);\n    }\n\n    function getWidth(border) {\n        return border.width;\n    }\n\n    function nonIgnoredElement(nodeContainer) {\n        return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || [\"SCRIPT\", \"HEAD\", \"TITLE\", \"OBJECT\", \"BR\", \"OPTION\"].indexOf(nodeContainer.node.nodeName) === -1);\n    }\n\n    function flatten(arrays) {\n        return [].concat.apply([], arrays);\n    }\n\n    function stripQuotes(content) {\n        var first = content.substr(0, 1);\n        return (first === content.substr(content.length - 1) && first.match(/'|\"/)) ? content.substr(1, content.length - 2) : content;\n    }\n\n    function getWords(characters) {\n        var words = [], i = 0, onWordBoundary = false, word;\n        while(characters.length) {\n            if (isWordBoundary(characters[i]) === onWordBoundary) {\n                word = characters.splice(0, i);\n                if (word.length) {\n                    words.push(punycode.ucs2.encode(word));\n                }\n                onWordBoundary =! onWordBoundary;\n                i = 0;\n            } else {\n                i++;\n            }\n\n            if (i >= characters.length) {\n                word = characters.splice(0, i);\n                if (word.length) {\n                    words.push(punycode.ucs2.encode(word));\n                }\n            }\n        }\n        return words;\n    }\n\n    function isWordBoundary(characterCode) {\n        return [\n                32, // <space>\n                13, // \\r\n                10, // \\n\n                9, // \\t\n                45 // -\n            ].indexOf(characterCode) !== -1;\n    }\n\n    function hasUnicode(string) {\n        return (/[^\\u0000-\\u00ff]/).test(string);\n    }\n\n    module.exports = NodeParser;\n\n},{\"./color\":3,\"./fontmetrics\":7,\"./log\":13,\"./nodecontainer\":14,\"./pseudoelementcontainer\":18,\"./stackingcontext\":21,\"./textcontainer\":25,\"./utils\":26,\"punycode\":1}],16:[function(_dereq_,module,exports){\n    var XHR = _dereq_('./xhr');\n    var utils = _dereq_('./utils');\n    var log = _dereq_('./log');\n    var createWindowClone = _dereq_('./clone');\n    var decode64 = utils.decode64;\n\n    function Proxy(src, proxyUrl, document) {\n        var supportsCORS = ('withCredentials' in new XMLHttpRequest());\n        if (!proxyUrl) {\n            return Promise.reject(\"No proxy configured\");\n        }\n        var callback = createCallback(supportsCORS);\n        var url = createProxyUrl(proxyUrl, src, callback);\n\n        return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {\n            return decode64(response.content);\n        }));\n    }\n    var proxyCount = 0;\n\n    function ProxyURL(src, proxyUrl, document) {\n        var supportsCORSImage = ('crossOrigin' in new Image());\n        var callback = createCallback(supportsCORSImage);\n        var url = createProxyUrl(proxyUrl, src, callback);\n        return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {\n            return \"data:\" + response.type + \";base64,\" + response.content;\n        }));\n    }\n\n    function jsonp(document, url, callback) {\n        return new Promise(function(resolve, reject) {\n            var s = document.createElement(\"script\");\n            var cleanup = function() {\n                delete window.html2canvas.proxy[callback];\n                document.body.removeChild(s);\n            };\n            window.html2canvas.proxy[callback] = function(response) {\n                cleanup();\n                resolve(response);\n            };\n            s.src = url;\n            s.onerror = function(e) {\n                cleanup();\n                reject(e);\n            };\n            document.body.appendChild(s);\n        });\n    }\n\n    function createCallback(useCORS) {\n        return !useCORS ? \"html2canvas_\" + Date.now() + \"_\" + (++proxyCount) + \"_\" + Math.round(Math.random() * 100000) : \"\";\n    }\n\n    function createProxyUrl(proxyUrl, src, callback) {\n        return proxyUrl + \"?url=\" + encodeURIComponent(src) + (callback.length ? \"&callback=html2canvas.proxy.\" + callback : \"\");\n    }\n\n    function documentFromHTML(src) {\n        return function(html) {\n            var parser = new DOMParser(), doc;\n            try {\n                doc = parser.parseFromString(html, \"text/html\");\n            } catch(e) {\n                log(\"DOMParser not supported, falling back to createHTMLDocument\");\n                doc = document.implementation.createHTMLDocument(\"\");\n                try {\n                    doc.open();\n                    doc.write(html);\n                    doc.close();\n                } catch(ee) {\n                    log(\"createHTMLDocument write not supported, falling back to document.body.innerHTML\");\n                    doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement\n                }\n            }\n\n            var b = doc.querySelector(\"base\");\n            if (!b || !b.href.host) {\n                var base = doc.createElement(\"base\");\n                base.href = src;\n                doc.head.insertBefore(base, doc.head.firstChild);\n            }\n\n            return doc;\n        };\n    }\n\n    function loadUrlDocument(src, proxy, document, width, height, options) {\n        return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) {\n            return createWindowClone(doc, document, width, height, options, 0, 0);\n        });\n    }\n\n    exports.Proxy = Proxy;\n    exports.ProxyURL = ProxyURL;\n    exports.loadUrlDocument = loadUrlDocument;\n\n},{\"./clone\":2,\"./log\":13,\"./utils\":26,\"./xhr\":28}],17:[function(_dereq_,module,exports){\n    var ProxyURL = _dereq_('./proxy').ProxyURL;\n\n    function ProxyImageContainer(src, proxy) {\n        var link = document.createElement(\"a\");\n        link.href = src;\n        src = link.href;\n        this.src = src;\n        this.image = new Image();\n        var self = this;\n        this.promise = new Promise(function(resolve, reject) {\n            self.image.crossOrigin = \"Anonymous\";\n            self.image.onload = resolve;\n            self.image.onerror = reject;\n\n            new ProxyURL(src, proxy, document).then(function(url) {\n                self.image.src = url;\n            })['catch'](reject);\n        });\n    }\n\n    module.exports = ProxyImageContainer;\n\n},{\"./proxy\":16}],18:[function(_dereq_,module,exports){\n    var NodeContainer = _dereq_('./nodecontainer');\n\n    function PseudoElementContainer(node, parent, type) {\n        NodeContainer.call(this, node, parent);\n        this.isPseudoElement = true;\n        this.before = type === \":before\";\n    }\n\n    PseudoElementContainer.prototype.cloneTo = function(stack) {\n        PseudoElementContainer.prototype.cloneTo.call(this, stack);\n        stack.isPseudoElement = true;\n        stack.before = this.before;\n    };\n\n    PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);\n\n    PseudoElementContainer.prototype.appendToDOM = function() {\n        if (this.before) {\n            this.parent.node.insertBefore(this.node, this.parent.node.firstChild);\n        } else {\n            this.parent.node.appendChild(this.node);\n        }\n        this.parent.node.className += \" \" + this.getHideClass();\n    };\n\n    PseudoElementContainer.prototype.cleanDOM = function() {\n        this.node.parentNode.removeChild(this.node);\n        this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), \"\");\n    };\n\n    PseudoElementContainer.prototype.getHideClass = function() {\n        return this[\"PSEUDO_HIDE_ELEMENT_CLASS_\" + (this.before ? \"BEFORE\" : \"AFTER\")];\n    };\n\n    PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = \"___html2canvas___pseudoelement_before\";\n    PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = \"___html2canvas___pseudoelement_after\";\n\n    module.exports = PseudoElementContainer;\n\n},{\"./nodecontainer\":14}],19:[function(_dereq_,module,exports){\n    var log = _dereq_('./log');\n\n    function Renderer(width, height, images, options, document) {\n        this.width = width;\n        this.height = height;\n        this.images = images;\n        this.options = options;\n        this.document = document;\n    }\n\n    Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {\n        var paddingLeft = container.cssInt('paddingLeft'),\n            paddingTop = container.cssInt('paddingTop'),\n            paddingRight = container.cssInt('paddingRight'),\n            paddingBottom = container.cssInt('paddingBottom'),\n            borders = borderData.borders;\n\n        var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight);\n        var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom);\n        this.drawImage(\n            imageContainer,\n            0,\n            0,\n            imageContainer.image.width || width,\n            imageContainer.image.height || height,\n            bounds.left + paddingLeft + borders[3].width,\n            bounds.top + paddingTop + borders[0].width,\n            width,\n            height\n        );\n    };\n\n    Renderer.prototype.renderBackground = function(container, bounds, borderData) {\n        if (bounds.height > 0 && bounds.width > 0) {\n            this.renderBackgroundColor(container, bounds);\n            this.renderBackgroundImage(container, bounds, borderData);\n        }\n    };\n\n    Renderer.prototype.renderBackgroundColor = function(container, bounds) {\n        var color = container.color(\"backgroundColor\");\n        if (!color.isTransparent()) {\n            this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color);\n        }\n    };\n\n    Renderer.prototype.renderBorders = function(borders) {\n        borders.forEach(this.renderBorder, this);\n    };\n\n    Renderer.prototype.renderBorder = function(data) {\n        if (!data.color.isTransparent() && data.args !== null) {\n            this.drawShape(data.args, data.color);\n        }\n    };\n\n    Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) {\n        var backgroundImages = container.parseBackgroundImages();\n        backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {\n            switch(backgroundImage.method) {\n                case \"url\":\n                    var image = this.images.get(backgroundImage.args[0]);\n                    if (image) {\n                        this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData);\n                    } else {\n                        log(\"Error loading background-image\", backgroundImage.args[0]);\n                    }\n                    break;\n                case \"linear-gradient\":\n                case \"gradient\":\n                    var gradientImage = this.images.get(backgroundImage.value);\n                    if (gradientImage) {\n                        this.renderBackgroundGradient(gradientImage, bounds, borderData);\n                    } else {\n                        log(\"Error loading background-image\", backgroundImage.args[0]);\n                    }\n                    break;\n                case \"none\":\n                    break;\n                default:\n                    log(\"Unknown background-image type\", backgroundImage.args[0]);\n            }\n        }, this);\n    };\n\n    Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) {\n        var size = container.parseBackgroundSize(bounds, imageContainer.image, index);\n        var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);\n        var repeat = container.parseBackgroundRepeat(index);\n        switch (repeat) {\n            case \"repeat-x\":\n            case \"repeat no-repeat\":\n                this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData);\n                break;\n            case \"repeat-y\":\n            case \"no-repeat repeat\":\n                this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData);\n                break;\n            case \"no-repeat\":\n                this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData);\n                break;\n            default:\n                this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]);\n                break;\n        }\n    };\n\n    module.exports = Renderer;\n\n},{\"./log\":13}],20:[function(_dereq_,module,exports){\n    var Renderer = _dereq_('../renderer');\n    var LinearGradientContainer = _dereq_('../lineargradientcontainer');\n    var log = _dereq_('../log');\n\n    function CanvasRenderer(width, height) {\n        Renderer.apply(this, arguments);\n        this.canvas = this.options.canvas || this.document.createElement(\"canvas\");\n        if (!this.options.canvas) {\n            this.canvas.width = width;\n            this.canvas.height = height;\n        }\n        this.ctx = this.canvas.getContext(\"2d\");\n        this.taintCtx = this.document.createElement(\"canvas\").getContext(\"2d\");\n        this.ctx.textBaseline = \"bottom\";\n        this.variables = {};\n        log(\"Initialized CanvasRenderer with size\", width, \"x\", height);\n    }\n\n    CanvasRenderer.prototype = Object.create(Renderer.prototype);\n\n    CanvasRenderer.prototype.setFillStyle = function(fillStyle) {\n        this.ctx.fillStyle = typeof(fillStyle) === \"object\" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle;\n        return this.ctx;\n    };\n\n    CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {\n        this.setFillStyle(color).fillRect(left, top, width, height);\n    };\n\n    CanvasRenderer.prototype.circle = function(left, top, size, color) {\n        this.setFillStyle(color);\n        this.ctx.beginPath();\n        this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true);\n        this.ctx.closePath();\n        this.ctx.fill();\n    };\n\n    CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) {\n        this.circle(left, top, size, color);\n        this.ctx.strokeStyle = strokeColor.toString();\n        this.ctx.stroke();\n    };\n\n    CanvasRenderer.prototype.drawShape = function(shape, color) {\n        this.shape(shape);\n        this.setFillStyle(color).fill();\n    };\n\n    CanvasRenderer.prototype.taints = function(imageContainer) {\n        if (imageContainer.tainted === null) {\n            this.taintCtx.drawImage(imageContainer.image, 0, 0);\n            try {\n                this.taintCtx.getImageData(0, 0, 1, 1);\n                imageContainer.tainted = false;\n            } catch(e) {\n                this.taintCtx = document.createElement(\"canvas\").getContext(\"2d\");\n                imageContainer.tainted = true;\n            }\n        }\n\n        return imageContainer.tainted;\n    };\n\n    CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {\n        if (!this.taints(imageContainer) || this.options.allowTaint) {\n            this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);\n        }\n    };\n\n    CanvasRenderer.prototype.clip = function(shapes, callback, context) {\n        this.ctx.save();\n        shapes.filter(hasEntries).forEach(function(shape) {\n            this.shape(shape).clip();\n        }, this);\n        callback.call(context);\n        this.ctx.restore();\n    };\n\n    CanvasRenderer.prototype.shape = function(shape) {\n        this.ctx.beginPath();\n        shape.forEach(function(point, index) {\n            if (point[0] === \"rect\") {\n                this.ctx.rect.apply(this.ctx, point.slice(1));\n            } else {\n                this.ctx[(index === 0) ? \"moveTo\" : point[0] + \"To\" ].apply(this.ctx, point.slice(1));\n            }\n        }, this);\n        this.ctx.closePath();\n        return this.ctx;\n    };\n\n    CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {\n        this.setFillStyle(color).font = [style, variant, weight, size, family].join(\" \").split(\",\")[0];\n    };\n\n    CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) {\n        this.setVariable(\"shadowColor\", color.toString())\n            .setVariable(\"shadowOffsetY\", offsetX)\n            .setVariable(\"shadowOffsetX\", offsetY)\n            .setVariable(\"shadowBlur\", blur);\n    };\n\n    CanvasRenderer.prototype.clearShadow = function() {\n        this.setVariable(\"shadowColor\", \"rgba(0,0,0,0)\");\n    };\n\n    CanvasRenderer.prototype.setOpacity = function(opacity) {\n        this.ctx.globalAlpha = opacity;\n    };\n\n    CanvasRenderer.prototype.setTransform = function(transform) {\n        this.ctx.translate(transform.origin[0], transform.origin[1]);\n        this.ctx.transform.apply(this.ctx, transform.matrix);\n        this.ctx.translate(-transform.origin[0], -transform.origin[1]);\n    };\n\n    CanvasRenderer.prototype.setVariable = function(property, value) {\n        if (this.variables[property] !== value) {\n            this.variables[property] = this.ctx[property] = value;\n        }\n\n        return this;\n    };\n\n    CanvasRenderer.prototype.text = function(text, left, bottom) {\n        this.ctx.fillText(text, left, bottom);\n    };\n\n    CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) {\n        var shape = [\n            [\"line\", Math.round(left), Math.round(top)],\n            [\"line\", Math.round(left + width), Math.round(top)],\n            [\"line\", Math.round(left + width), Math.round(height + top)],\n            [\"line\", Math.round(left), Math.round(height + top)]\n        ];\n        this.clip([shape], function() {\n            this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);\n        }, this);\n    };\n\n    CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) {\n        var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop);\n        this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), \"repeat\"));\n        this.ctx.translate(offsetX, offsetY);\n        this.ctx.fill();\n        this.ctx.translate(-offsetX, -offsetY);\n    };\n\n    CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) {\n        if (gradientImage instanceof LinearGradientContainer) {\n            var gradient = this.ctx.createLinearGradient(\n                bounds.left + bounds.width * gradientImage.x0,\n                bounds.top + bounds.height * gradientImage.y0,\n                bounds.left +  bounds.width * gradientImage.x1,\n                bounds.top +  bounds.height * gradientImage.y1);\n            gradientImage.colorStops.forEach(function(colorStop) {\n                gradient.addColorStop(colorStop.stop, colorStop.color.toString());\n            });\n            this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient);\n        }\n    };\n\n    CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {\n        var image = imageContainer.image;\n        if(image.width === size.width && image.height === size.height) {\n            return image;\n        }\n\n        var ctx, canvas = document.createElement('canvas');\n        canvas.width = size.width;\n        canvas.height = size.height;\n        ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );\n        return canvas;\n    };\n\n    function hasEntries(array) {\n        return array.length > 0;\n    }\n\n    module.exports = CanvasRenderer;\n\n},{\"../lineargradientcontainer\":12,\"../log\":13,\"../renderer\":19}],21:[function(_dereq_,module,exports){\n    var NodeContainer = _dereq_('./nodecontainer');\n\n    function StackingContext(hasOwnStacking, opacity, element, parent) {\n        NodeContainer.call(this, element, parent);\n        this.ownStacking = hasOwnStacking;\n        this.contexts = [];\n        this.children = [];\n        this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;\n    }\n\n    StackingContext.prototype = Object.create(NodeContainer.prototype);\n\n    StackingContext.prototype.getParentStack = function(context) {\n        var parentStack = (this.parent) ? this.parent.stack : null;\n        return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;\n    };\n\n    module.exports = StackingContext;\n\n},{\"./nodecontainer\":14}],22:[function(_dereq_,module,exports){\n    function Support(document) {\n        this.rangeBounds = this.testRangeBounds(document);\n        this.cors = this.testCORS();\n        this.svg = this.testSVG();\n    }\n\n    Support.prototype.testRangeBounds = function(document) {\n        var range, testElement, rangeBounds, rangeHeight, support = false;\n\n        if (document.createRange) {\n            range = document.createRange();\n            if (range.getBoundingClientRect) {\n                testElement = document.createElement('boundtest');\n                testElement.style.height = \"123px\";\n                testElement.style.display = \"block\";\n                document.body.appendChild(testElement);\n\n                range.selectNode(testElement);\n                rangeBounds = range.getBoundingClientRect();\n                rangeHeight = rangeBounds.height;\n\n                if (rangeHeight === 123) {\n                    support = true;\n                }\n                document.body.removeChild(testElement);\n            }\n        }\n\n        return support;\n    };\n\n    Support.prototype.testCORS = function() {\n        return typeof((new Image()).crossOrigin) !== \"undefined\";\n    };\n\n    Support.prototype.testSVG = function() {\n        var img = new Image();\n        var canvas = document.createElement(\"canvas\");\n        var ctx =  canvas.getContext(\"2d\");\n        img.src = \"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>\";\n\n        try {\n            ctx.drawImage(img, 0, 0);\n            canvas.toDataURL();\n        } catch(e) {\n            return false;\n        }\n        return true;\n    };\n\n    module.exports = Support;\n\n},{}],23:[function(_dereq_,module,exports){\n    var XHR = _dereq_('./xhr');\n    var decode64 = _dereq_('./utils').decode64;\n\n    function SVGContainer(src) {\n        this.src = src;\n        this.image = null;\n        var self = this;\n\n        this.promise = this.hasFabric().then(function() {\n            return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src));\n        }).then(function(svg) {\n            return new Promise(function(resolve) {\n                window.html2canvas.svg.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve));\n            });\n        });\n    }\n\n    SVGContainer.prototype.hasFabric = function() {\n        return !window.html2canvas.svg || !window.html2canvas.svg.fabric ? Promise.reject(new Error(\"html2canvas.svg.js is not loaded, cannot render svg\")) : Promise.resolve();\n    };\n\n    SVGContainer.prototype.inlineFormatting = function(src) {\n        return (/^data:image\\/svg\\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src);\n    };\n\n    SVGContainer.prototype.removeContentType = function(src) {\n        return src.replace(/^data:image\\/svg\\+xml(;base64)?,/,'');\n    };\n\n    SVGContainer.prototype.isInline = function(src) {\n        return (/^data:image\\/svg\\+xml/i.test(src));\n    };\n\n    SVGContainer.prototype.createCanvas = function(resolve) {\n        var self = this;\n        return function (objects, options) {\n            var canvas = new window.html2canvas.svg.fabric.StaticCanvas('c');\n            self.image = canvas.lowerCanvasEl;\n            canvas\n                .setWidth(options.width)\n                .setHeight(options.height)\n                .add(window.html2canvas.svg.fabric.util.groupSVGElements(objects, options))\n                .renderAll();\n            resolve(canvas.lowerCanvasEl);\n        };\n    };\n\n    SVGContainer.prototype.decode64 = function(str) {\n        return (typeof(window.atob) === \"function\") ? window.atob(str) : decode64(str);\n    };\n\n    module.exports = SVGContainer;\n\n},{\"./utils\":26,\"./xhr\":28}],24:[function(_dereq_,module,exports){\n    var SVGContainer = _dereq_('./svgcontainer');\n\n    function SVGNodeContainer(node, _native) {\n        this.src = node;\n        this.image = null;\n        var self = this;\n\n        this.promise = _native ? new Promise(function(resolve, reject) {\n            self.image = new Image();\n            self.image.onload = resolve;\n            self.image.onerror = reject;\n            self.image.src = \"data:image/svg+xml,\" + (new XMLSerializer()).serializeToString(node);\n            if (self.image.complete === true) {\n                resolve(self.image);\n            }\n        }) : this.hasFabric().then(function() {\n            return new Promise(function(resolve) {\n                window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));\n            });\n        });\n    }\n\n    SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);\n\n    module.exports = SVGNodeContainer;\n\n},{\"./svgcontainer\":23}],25:[function(_dereq_,module,exports){\n    var NodeContainer = _dereq_('./nodecontainer');\n\n    function TextContainer(node, parent) {\n        NodeContainer.call(this, node, parent);\n    }\n\n    TextContainer.prototype = Object.create(NodeContainer.prototype);\n\n    TextContainer.prototype.applyTextTransform = function() {\n        this.node.data = this.transform(this.parent.css(\"textTransform\"));\n    };\n\n    TextContainer.prototype.transform = function(transform) {\n        var text = this.node.data;\n        switch(transform){\n            case \"lowercase\":\n                return text.toLowerCase();\n            case \"capitalize\":\n                return text.replace(/(^|\\s|:|-|\\(|\\))([a-z])/g, capitalize);\n            case \"uppercase\":\n                return text.toUpperCase();\n            default:\n                return text;\n        }\n    };\n\n    function capitalize(m, p1, p2) {\n        if (m.length > 0) {\n            return p1 + p2.toUpperCase();\n        }\n    }\n\n    module.exports = TextContainer;\n\n},{\"./nodecontainer\":14}],26:[function(_dereq_,module,exports){\n    exports.smallImage = function smallImage() {\n        return \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\";\n    };\n\n    exports.bind = function(callback, context) {\n        return function() {\n            return callback.apply(context, arguments);\n        };\n    };\n\n    /*\n     * base64-arraybuffer\n     * https://github.com/niklasvh/base64-arraybuffer\n     *\n     * Copyright (c) 2012 Niklas von Hertzen\n     * Licensed under the MIT license.\n     */\n\n    exports.decode64 = function(base64) {\n        var chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n        var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3;\n\n        var output = \"\";\n\n        for (i = 0; i < len; i+=4) {\n            encoded1 = chars.indexOf(base64[i]);\n            encoded2 = chars.indexOf(base64[i+1]);\n            encoded3 = chars.indexOf(base64[i+2]);\n            encoded4 = chars.indexOf(base64[i+3]);\n\n            byte1 = (encoded1 << 2) | (encoded2 >> 4);\n            byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);\n            byte3 = ((encoded3 & 3) << 6) | encoded4;\n            if (encoded3 === 64) {\n                output += String.fromCharCode(byte1);\n            } else if (encoded4 === 64 || encoded4 === -1) {\n                output += String.fromCharCode(byte1, byte2);\n            } else{\n                output += String.fromCharCode(byte1, byte2, byte3);\n            }\n        }\n\n        return output;\n    };\n\n    exports.getBounds = function(node) {\n        if (node.getBoundingClientRect) {\n            var clientRect = node.getBoundingClientRect();\n            var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;\n            return {\n                top: clientRect.top,\n                bottom: clientRect.bottom || (clientRect.top + clientRect.height),\n                right: clientRect.left + width,\n                left: clientRect.left,\n                width:  width,\n                height: node.offsetHeight == null ? clientRect.height : node.offsetHeight\n            };\n        }\n        return {};\n    };\n\n    exports.offsetBounds = function(node) {\n        var parent = node.offsetParent ? exports.offsetBounds(node.offsetParent) : {top: 0, left: 0};\n\n        return {\n            top: node.offsetTop + parent.top,\n            bottom: node.offsetTop + node.offsetHeight + parent.top,\n            right: node.offsetLeft + parent.left + node.offsetWidth,\n            left: node.offsetLeft + parent.left,\n            width: node.offsetWidth,\n            height: node.offsetHeight\n        };\n    };\n\n    exports.parseBackgrounds = function(backgroundImage) {\n        var whitespace = ' \\r\\n\\t',\n            method, definition, prefix, prefix_i, block, results = [],\n            mode = 0, numParen = 0, quote, args;\n        var appendResult = function() {\n            if(method) {\n                if (definition.substr(0, 1) === '\"') {\n                    definition = definition.substr(1, definition.length - 2);\n                }\n                if (definition) {\n                    args.push(definition);\n                }\n                if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {\n                    prefix = method.substr(0, prefix_i);\n                    method = method.substr(prefix_i);\n                }\n                results.push({\n                    prefix: prefix,\n                    method: method.toLowerCase(),\n                    value: block,\n                    args: args,\n                    image: null\n                });\n            }\n            args = [];\n            method = prefix = definition = block = '';\n        };\n        args = [];\n        method = prefix = definition = block = '';\n        backgroundImage.split(\"\").forEach(function(c) {\n            if (mode === 0 && whitespace.indexOf(c) > -1) {\n                return;\n            }\n            switch(c) {\n                case '\"':\n                    if(!quote) {\n                        quote = c;\n                    } else if(quote === c) {\n                        quote = null;\n                    }\n                    break;\n                case '(':\n                    if(quote) {\n                        break;\n                    } else if(mode === 0) {\n                        mode = 1;\n                        block += c;\n                        return;\n                    } else {\n                        numParen++;\n                    }\n                    break;\n                case ')':\n                    if (quote) {\n                        break;\n                    } else if(mode === 1) {\n                        if(numParen === 0) {\n                            mode = 0;\n                            block += c;\n                            appendResult();\n                            return;\n                        } else {\n                            numParen--;\n                        }\n                    }\n                    break;\n\n                case ',':\n                    if (quote) {\n                        break;\n                    } else if(mode === 0) {\n                        appendResult();\n                        return;\n                    } else if (mode === 1) {\n                        if (numParen === 0 && !method.match(/^url$/i)) {\n                            args.push(definition);\n                            definition = '';\n                            block += c;\n                            return;\n                        }\n                    }\n                    break;\n            }\n\n            block += c;\n            if (mode === 0) {\n                method += c;\n            } else {\n                definition += c;\n            }\n        });\n\n        appendResult();\n        return results;\n    };\n\n},{}],27:[function(_dereq_,module,exports){\n    var GradientContainer = _dereq_('./gradientcontainer');\n\n    function WebkitGradientContainer(imageData) {\n        GradientContainer.apply(this, arguments);\n        this.type = imageData.args[0] === \"linear\" ? GradientContainer.TYPES.LINEAR : GradientContainer.TYPES.RADIAL;\n    }\n\n    WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);\n\n    module.exports = WebkitGradientContainer;\n\n},{\"./gradientcontainer\":9}],28:[function(_dereq_,module,exports){\n    function XHR(url) {\n        return new Promise(function(resolve, reject) {\n            var xhr = new XMLHttpRequest();\n            xhr.open('GET', url);\n\n            xhr.onload = function() {\n                if (xhr.status === 200) {\n                    resolve(xhr.responseText);\n                } else {\n                    reject(new Error(xhr.statusText));\n                }\n            };\n\n            xhr.onerror = function() {\n                reject(new Error(\"Network Error\"));\n            };\n\n            xhr.send();\n        });\n    }\n\n    module.exports = XHR;\n\n},{}]},{},[4])(4)\n});"
  }
]