diff --git a/client/naturalCrit/homebrew/editor/editor.jsx b/client/naturalCrit/homebrew/editor/editor.jsx
index 864e3fb..74608a0 100644
--- a/client/naturalCrit/homebrew/editor/editor.jsx
+++ b/client/naturalCrit/homebrew/editor/editor.jsx
@@ -36,6 +36,22 @@ var Icons = [
snippet : Snippets.statBlock,
tooltip : 'Monster Stat Block'
},
+ {
+ icon : 'fa-table',
+ snippet : "",
+ tooltip : "Class Table"
+ },
+ {
+ icon : 'fa-code',
+ snippet : "```\n```",
+ tooltip : "Column Break"
+ },
+ {
+ icon : 'fa-file-o',
+ snippet : "\\page",
+ tooltip : "New Page"
+ },
+
]
diff --git a/client/naturalCrit/homebrew/editor/snippets.js b/client/naturalCrit/homebrew/editor/snippets.js
index 6376568..8be8686 100644
--- a/client/naturalCrit/homebrew/editor/snippets.js
+++ b/client/naturalCrit/homebrew/editor/snippets.js
@@ -18,16 +18,19 @@ module.exports = {
"As a paladin, you gain the following class features.",
"",
"#### Hit Points",
- "**Hit Dice:** 1d10 per paladin level
",
- "**Hit Points at 1st Level:** 10 + your Constitution modifier
",
- "**Hit Points at Higher Levels:** 1d10 (or 6) + your Constituion modifier per paladin level after 1st",
+ "___",
+ "- **Hit Dice:** 1d10 per paladin level",
+ "- **Hit Points at 1st Level:** 10 + your Constitution modifier",
+ "- **Hit Points at Higher Levels:** 1d10 (or 6) + your Constituion modifier per paladin level after 1st",
"",
"#### Proficiencies",
- "**Armor:** All armor, Shields
",
- "**Weapons:** Simple Weapons, martial weapons
",
- "**Tools:** None
",
- "**Saving Throws:** Wisdom, Charisma
",
- "**Skills:** Choose two from Athletics, Insight, Intimidation, Medicine, Persuasion, and Religion",
+ "___",
+ "- **Armor:** All armor, Shields",
+ "- **Weapons:** Simple Weapons, martial weapons",
+ "- **Tools:** None
",
+ "___",
+ "- **Saving Throws:** Wisdom, Charisma",
+ "- **Skills:** Choose two from Athletics, Insight, Intimidation, Medicine, Persuasion, and Religion",
"",
"#### Equipment",
"You start with the following equipment, in addition to the equipment granted by your background:",
@@ -39,11 +42,13 @@ module.exports = {
spell : [
"#### Continual Flame",
- "*2nd-level evocation*
",
- "**Casting Time:** 1 action
",
- "**Range:** Touch
",
- "**Components:** V, S, M (ruby dust worth 50gp, which the spell consumes)
",
- "**Duration:** Until dispelled
",
+ "*2nd-level evocation*",
+ "___",
+ "- **Casting Time:** 1 action",
+ "- **Range:** Touch",
+ "- **Components:** V, S, M (ruby dust worth 50gp, which the spell consumes)",
+ "- **Duration:** Until dispelled",
+ "",
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
"A *continual flame* can be covered or hidden but not smothered or quenched."
@@ -75,16 +80,30 @@ module.exports = {
].join('\n'),
statBlock :[
- "## Warhorse",
- "*Large beast, unaligned*",
- "",
- "---",
- "|STR|DEX|CON|INT|WIS|CHA|",
- "|:---:|:---:|:---:|:---:|:---:|:---:|:---:|",
- "|18 (+4)|18 (+4)|18 (+4)|18 (+4)|18 (+4)|18 (+4)|",
- "---",
- "***Trampling Charge*** Does a thing yo",
-
+ "___",
+ "> ## Warhorse",
+ ">*Large beast, unaligned*",
+ "> ___",
+ "> - **Armor Class** 18 (natural armor)",
+ "> - **Hit Points** 33 (6d8 + 6)",
+ "> - **Speed** 25ft",
+ ">___",
+ ">|STR|DEX|CON|INT|WIS|CHA|",
+ ">|:---:|:---:|:---:|:---:|:---:|:---:|:---:|",
+ ">|18 (+4)|18 (+4)|18 (+4)|18 (+4)|18 (+4)|18 (+4)|",
+ ">___",
+ "> - **Damage Immunities** poison, psychic",
+ "> - **Condition Immunities** blinded, charmed, deafened, exhaustion, frightened, paralyzed, petrified, poisoned",
+ "> - **Languages** None",
+ "> - **Challenge** 1 (200 XP)",
+ "> ___",
+ "> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
+ ">",
+ "> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
+ "> ### Actions",
+ "> ***Multiattack.*** The armor makes two two melee attacks.",
+ ">",
+ "> ***Slam.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ",
].join('\n')
diff --git a/client/naturalCrit/homebrew/phb/phb.jsx b/client/naturalCrit/homebrew/phb/phb.jsx
index 2cc8467..e7d7307 100644
--- a/client/naturalCrit/homebrew/phb/phb.jsx
+++ b/client/naturalCrit/homebrew/phb/phb.jsx
@@ -12,8 +12,8 @@ var Phb = React.createClass({
},
renderPages : function(){
- return _.map(this.props.text.split('\page'), (pageText, index) => {
- return
+ return _.map(this.props.text.split('\\page'), (pageText, index) => {
+ return
})
},
diff --git a/client/naturalCrit/homebrew/phb/phb.less b/client/naturalCrit/homebrew/phb/phb.less
index 249fff4..09efa1e 100644
--- a/client/naturalCrit/homebrew/phb/phb.less
+++ b/client/naturalCrit/homebrew/phb/phb.less
@@ -25,10 +25,12 @@
font-family : ScalaSans;
src : url('/assets/naturalCrit/homebrew/assets/Scaly Sans.otf');
}
+/*
@font-face {
font-family : ScalaSansBold;
src : url('/assets/naturalCrit/homebrew/assets/Scala Sans Bold.ttf');
}
+*/
@font-face {
font-family : ScalaSansSmallCaps;
src : url('/assets/naturalCrit/homebrew/assets/Scala Sans SmallCaps.ttf');
@@ -54,21 +56,26 @@
padding : 1.0cm 1.7cm;
column-count : 2;
column-fill : auto;
+ column-gap : 1cm;
column-width : 8cm;
background-image : url('/assets/naturalCrit/homebrew/assets/PHB-background.png');
-webkit-column-count : 2;
-moz-column-count : 2;
-webkit-column-width : 8cm;
-moz-column-width : 8cm;
- column-gap : 1cm;
- -webkit-column-gap : 1cm;
+ -webkit-column-gap : 1cm;
text-rendering : optimizeLegibility;
- p,ul{
+
+
+ p,ul,blockquote{
-webkit-column-break-inside : avoid;
-moz-column-break-inside : avoid;
-o-column-break-inside : avoid;
-ms-column-break-inside : avoid;
column-break-inside : avoid;
+ }
+
+ p,ul{
strong{
font-family : BookInsanityBold;
em{
@@ -97,6 +104,12 @@
list-style-position : inside;
list-style-type : disc;
}
+
+
+ hr+table{
+ -webkit-column-span : all;
+ column-span : all;
+ }
table{
width : 100%;
margin-bottom : 1em;
@@ -117,38 +130,152 @@
}
}
}
- blockquote{
- background-color : @green;
- font-family : ScalaSans;
- box-sizing: border-box;
- padding : 5px 10px;
- border-top : 2px black solid;
- border-bottom : 2px black solid;
- box-shadow: 1px 4px 14px #888;
- margin-bottom: 1em;
- p{
- font-family : ScalaSans;
- font-size: 10pt;
- line-height: 1.1em;
- em{
- font-family : ScalaSans;
- font-style: italic;
+ hr{
+ visibility: hidden;
+ //border : none;
+ margin: 0px;
+ }
+
+ //Fancy List of things
+ hr+ul{
+ list-style-type: none;
+ text-indent: -1em;
+ padding-left: 1em;
+
+ //margin-top: -0.5em;
+ margin-bottom: 0.5em;
+ }
+
+ //Column Break
+ pre{
+ visibility: hidden;
+ -webkit-column-break-after: always;
+ }
+
+ //Monster Stat block
+ hr+blockquote{
+ position : relative;
+ background-color: #FDF1DC;
+ border : none;
+
+ h2{
+ margin-bottom: 0px;
+ &+p{
+ padding-bottom: 0px;
}
+ }
+ h3{
+ font-family: ScalaSans;
+ font-weight: 400;
+ border-bottom: 1px solid @header;
+ }
+
+ ul{
+ list-style-type: none;
+ font-family: ScalaSans;
+ color : @header;
+ font-size: 10pt;
+ text-indent: -1em;
+ padding-left: 1em;
strong{
+ font-family: ScalaSans;
font-weight: 800;
- em{
- font-weight: 800;
- font-style: italic;
- }
+ }
+ }
+
+ hr+table{
+ -webkit-column-span : 1;
+ column-span : 1;
+ }
+ table{
+ color : @header;
+ }
+
+ p+p{
+ margin-top : 0em;
+ text-indent : 0em;
+ padding-bottom: 0.5em;
+ }
+
+
+
+ //Maybe move this to be built in
+ hr{
+ visibility: visible;
+ margin: 8px 0px;
+
+ border-color : transparent;
+ position: relative;
+ @height : 3px;
+ &:after, &:before{
+ content : "";
+ position: absolute;
+ width: 100%;
+ height: @height;
+ left: 0px;
+ }
+ &:before{
+ top : -@height;
+ background: linear-gradient(to right top, @horizontalRule 40%, transparent 50%)
+ }
+ &:after{
+ top: 0px;
+ background: linear-gradient(to right bottom, @horizontalRule 40%, transparent 50%)
}
}
+
+ //Borders
+ &:after, &:before{
+ content : "";
+ position: absolute;
+ background-color : #E69A28;
+ border: 1px solid black;
+ height : 4px;
+ width : 100%;
+ padding : 0px 3px;
+ }
+ &:before{
+ top : 0px;
+ left : -3px;
+ }
+ &:after{
+ bottom : 0px;
+ left : -3px;
+ }
}
+
+ //Note
+ blockquote{
+ box-sizing : border-box;
+ margin-bottom : 1em;
+ padding : 5px 10px;
+ background-color : @green;
+ font-family : ScalaSans;
+ border-top : 2px black solid;
+ border-bottom : 2px black solid;
+ box-shadow : 1px 4px 14px #888;
+ p{
+ font-family : ScalaSans;
+ font-size : 10pt;
+ line-height : 1.1em;
+ em{
+ font-family : ScalaSans;
+ font-style : italic;
+ }
+ strong{
+ font-weight : 800;
+ font-family : ScalaSans;
+ em{
+ font-style : italic;
+ font-weight : 800;
+ }
+ }
+ }
+ }
pre{
-
}
-
h1,h2,h3,h4{
margin-top : 0.2em;
margin-bottom : 0.2em;
@@ -166,7 +293,7 @@
margin-top : 20px;
margin-bottom : 10px;
font-family : Solbera;
- font-size : 5em;
+ font-size : 4.5em;
}
}
}
@@ -178,8 +305,8 @@
border-bottom : 2px solid @headerUnderline;
}
h4{
- font-size : 12pt;
- margin-bottom: 0.00em;
+ margin-bottom : 0.00em;
+ font-size : 12pt;
}
h5{
margin-bottom : 0.2em;
diff --git a/package.json b/package.json
index d6c2127..82602a6 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,12 @@
"jsoneditor": "^4.2.1",
"lodash": "^3.10.1",
"marked": "^0.3.5",
+ "mongoose": "^4.3.3",
"pico-flux": "^1.1.0",
"pico-router": "^1.0.0",
"react": "^0.14.2",
"react-dom": "^0.14.2",
+ "shortid": "^2.2.4",
"vitreum": "^3.1.1"
}
}
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
new file mode 100644
index 0000000..e69de29
diff --git a/server/homebrew.model.js b/server/homebrew.model.js
new file mode 100644
index 0000000..f88b73e
--- /dev/null
+++ b/server/homebrew.model.js
@@ -0,0 +1,18 @@
+var mongoose = require('mongoose');
+var shortid = require('shortid');
+
+var HomebrewSchema = mongoose.Schema({
+ shareId : {type : String, default: shortid.generate},
+ editId : {type : String, default: shortid.generate},
+ text : {type : String, default : ""},
+
+ created : { type: Date, default: Date.now },
+});
+
+
+var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
+
+module.exports = {
+ schema : HomebrewSchema,
+ model : Homebrew,
+}
diff --git a/shared/naturalCrit/html2canvas.js b/shared/naturalCrit/html2canvas.js
new file mode 100644
index 0000000..8792636
--- /dev/null
+++ b/shared/naturalCrit/html2canvas.js
@@ -0,0 +1,3375 @@
+/*
+ html2canvas 0.5.0-alpha1
+ Copyright (c) 2015 Niklas von Hertzen
+
+ Released under MIT License
+*/
+
+(function(window, document, exports, global, define, undefined){
+
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license Licensed under MIT license
+ * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version 2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ var result = [];
+ while (length--) {
+ result[length] = fn(array[length]);
+ }
+ return result;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings or email
+ * addresses.
+ * @private
+ * @param {String} domain The domain name or email address.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ var parts = string.split('@');
+ var result = '';
+ if (parts.length > 1) {
+ // In email addresses, only the domain name should be punycoded. Leave
+ // the local part (i.e. everything up to `@`) intact.
+ result = parts[0] + '@';
+ string = parts[1];
+ }
+ var labels = string.split(regexSeparators);
+ var encoded = map(labels, fn).join('.');
+ return result + encoded;
+ }
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of numeric code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of numeric code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic numeric code point value.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ if (codePoint - 48 < 10) {
+ return codePoint - 22;
+ }
+ if (codePoint - 65 < 26) {
+ return codePoint - 65;
+ }
+ if (codePoint - 97 < 26) {
+ return codePoint - 97;
+ }
+ return base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if `flag` is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII-only symbols.
+ * @returns {String} The resulting string of Unicode symbols.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode symbols (e.g. a domain name label) to a
+ * Punycode string of ASCII-only symbols.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode symbols.
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's state to ,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name or an email address
+ * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
+ * it doesn't matter if you call it on a string that has already been
+ * converted to Unicode.
+ * @memberOf punycode
+ * @param {String} input The Punycoded domain name or email address to
+ * convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(input) {
+ return mapDomain(input, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name or an email address to
+ * Punycode. Only the non-ASCII parts of the domain name will be converted,
+ * i.e. it doesn't matter if you call it with a domain that's already in
+ * ASCII.
+ * @memberOf punycode
+ * @param {String} input The domain name or email address to convert, as a
+ * Unicode string.
+ * @returns {String} The Punycode representation of the given domain name or
+ * email address.
+ */
+ function toASCII(input) {
+ return mapDomain(input, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.3.1',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to Unicode code points, and back.
+ * @see
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define('punycode', function() {
+ return punycode;
+ });
+ } else if (freeExports && freeModule) {
+ if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+}(this));
+
+var html2canvasNodeAttribute = "data-html2canvas-node";
+var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
+var html2canvasCanvasCloneIndex = 0;
+var html2canvasCloneIndex = 0;
+
+window.html2canvas = function(nodeList, options) {
+ var index = html2canvasCloneIndex++;
+ options = options || {};
+ if (options.logging) {
+ window.html2canvas.logging = true;
+ window.html2canvas.start = Date.now();
+ }
+
+ options.async = typeof(options.async) === "undefined" ? true : options.async;
+ options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
+ options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
+ options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
+ options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
+ options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer;
+ options.strict = !!options.strict;
+
+ if (typeof(nodeList) === "string") {
+ if (typeof(options.proxy) !== "string") {
+ return Promise.reject("Proxy must be used when rendering url");
+ }
+ var width = options.width != null ? options.width : window.innerWidth;
+ var height = options.height != null ? options.height : window.innerHeight;
+ return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {
+ return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);
+ });
+ }
+
+ var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
+ node.setAttribute(html2canvasNodeAttribute + index, index);
+ return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
+ if (typeof(options.onrendered) === "function") {
+ log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
+ options.onrendered(canvas);
+ }
+ return canvas;
+ });
+};
+
+window.html2canvas.punycode = this.punycode;
+window.html2canvas.proxy = {};
+
+function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {
+ return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {
+ log("Document cloned");
+ var attributeName = html2canvasNodeAttribute + html2canvasIndex;
+ var selector = "[" + attributeName + "='" + html2canvasIndex + "']";
+ document.querySelector(selector).removeAttribute(attributeName);
+ var clonedWindow = container.contentWindow;
+ var node = clonedWindow.document.querySelector(selector);
+ var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
+ return oncloneHandler.then(function() {
+ return renderWindow(node, container, options, windowWidth, windowHeight);
+ });
+ });
+}
+
+function renderWindow(node, container, options, windowWidth, windowHeight) {
+ var clonedWindow = container.contentWindow;
+ var support = new Support(clonedWindow.document);
+ var imageLoader = new ImageLoader(options, support);
+ var bounds = getBounds(node);
+ var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
+ var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
+ var renderer = new options.renderer(width, height, imageLoader, options, document);
+ var parser = new NodeParser(node, renderer, support, imageLoader, options);
+ return parser.ready.then(function() {
+ log("Finished rendering");
+ var canvas;
+
+ if (options.type === "view") {
+ canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
+ } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
+ canvas = renderer.canvas;
+ } else {
+ 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: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset});
+ }
+
+ cleanupContainer(container, options);
+ return canvas;
+ });
+}
+
+function cleanupContainer(container, options) {
+ if (options.removeContainer) {
+ container.parentNode.removeChild(container);
+ log("Cleaned up container");
+ }
+}
+
+function crop(canvas, bounds) {
+ var croppedCanvas = document.createElement("canvas");
+ var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
+ var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
+ var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
+ var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
+ croppedCanvas.width = bounds.width;
+ croppedCanvas.height = bounds.height;
+ log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1));
+ log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1);
+ croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1);
+ return croppedCanvas;
+}
+
+function documentWidth (doc) {
+ return Math.max(
+ Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
+ Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
+ Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
+ );
+}
+
+function documentHeight (doc) {
+ return Math.max(
+ Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
+ Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
+ Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
+ );
+}
+
+function smallImage() {
+ return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
+}
+
+function isIE9() {
+ return document.documentMode && document.documentMode <= 9;
+}
+
+// https://github.com/niklasvh/html2canvas/issues/503
+function cloneNodeIE9(node, javascriptEnabled) {
+ var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
+
+ var child = node.firstChild;
+ while(child) {
+ if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
+ clone.appendChild(cloneNodeIE9(child, javascriptEnabled));
+ }
+ child = child.nextSibling;
+ }
+
+ return clone;
+}
+
+function createWindowClone(ownerDocument, containerDocument, width, height, options, x ,y) {
+ labelCanvasElements(ownerDocument);
+ var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
+ var container = containerDocument.createElement("iframe");
+
+ container.className = "html2canvas-container";
+ container.style.visibility = "hidden";
+ container.style.position = "fixed";
+ container.style.left = "-10000px";
+ container.style.top = "0px";
+ container.style.border = "0";
+ container.width = width;
+ container.height = height;
+ container.scrolling = "no"; // ios won't scroll without it
+ containerDocument.body.appendChild(container);
+
+ return new Promise(function(resolve) {
+ var documentClone = container.contentWindow.document;
+
+ cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
+ cloneNodeValues(ownerDocument.documentElement, documentElement, "select");
+
+ /* Chrome doesn't detect relative background-images assigned in inline