From ad569f04d8c06d8f94e83b375f4df19be7d24689 Mon Sep 17 00:00:00 2001 From: Scott Tolksdorf Date: Sat, 19 Dec 2015 18:26:35 -0500 Subject: [PATCH] Finished the monster stat block, using hr's as element modifiers, kinda weird. --- client/naturalCrit/homebrew/editor/editor.jsx | 16 + .../naturalCrit/homebrew/editor/snippets.js | 65 +- client/naturalCrit/homebrew/phb/phb.jsx | 4 +- client/naturalCrit/homebrew/phb/phb.less | 183 +- package.json | 2 + server/homebrew.api.js | 0 server/homebrew.model.js | 18 + shared/naturalCrit/html2canvas.js | 3375 +++++++++++++++++ 8 files changed, 3610 insertions(+), 53 deletions(-) create mode 100644 server/homebrew.api.js create mode 100644 server/homebrew.model.js create mode 100644 shared/naturalCrit/html2canvas.js 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