- 
      
- 
        Save mattgaspar/170e00731711e8bb94e1 to your computer and use it in GitHub Desktop. 
| jSigHelper = (function() { | |
| var chunkSeparator = '_' | |
| , charmap = {} // {'1':'g','2':'h','3':'i','4':'j','5':'k','6':'l','7':'m','8':'n','9':'o','a':'p','b':'q','c':'r','d':'s','e':'t','f':'u','0':'v'} | |
| , charmap_reverse = {} // will be filled by 'uncompress*" function | |
| // need to split below for IE7 (possibly others), which does not understand string[position] it seems (returns undefined) | |
| , allchars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX'.split('') | |
| , bitness = allchars.length / 2 | |
| , minus = 'Z' | |
| , plus = 'Y' | |
| for(var i = bitness-1; i > -1; i--){ | |
| charmap[allchars[i]] = allchars[i+bitness] | |
| charmap_reverse[allchars[i+bitness]] = allchars[i] | |
| } | |
| var remapTailChars = function(number){ | |
| // for any given number as string, returning string with trailing chars remapped something like so: | |
| // '345' -> '3de' | |
| var chars = number.split('') | |
| , l = chars.length | |
| // we are skipping first char. standard hex number char = delimiter | |
| for (var i = 1; i < l; i++ ){ | |
| chars[i] = charmap[chars[i]] | |
| } | |
| return chars.join('') | |
| } | |
| , compressstrokeleg = function(data){ | |
| // we convert half-stroke (only 'x' series or only 'y' series of numbers) | |
| // data is like this: | |
| // [517,516,514,513,513,513,514,516,519,524,529,537,541,543,544,544,539,536] | |
| // that is converted into this: | |
| // "5agm12100p1235584210m53" | |
| // each number in the chain is converted such: | |
| // - find diff from previous number | |
| // - first significant digit is kept as digit char. digit char = start of new number. | |
| // - consecutive numbers are mapped to letters, where 1 to 9 are A to I, 0 is O | |
| // Sign changes are denoted by "P" - plus, "M" for minus. | |
| var answer = [] | |
| , lastwhole = 0 | |
| , last = 0 | |
| , lastpolarity = 1 | |
| , l = data.length | |
| , nwhole, n, absn | |
| for(var i = 0; i < l; i++){ | |
| // we start with whole coordinates for each point | |
| // coords are converted into series of vectors: | |
| // [512, 514, 520] | |
| // [512, +2, +6] | |
| nwhole = Math.round(data[i]) | |
| n = nwhole - lastwhole | |
| lastwhole = nwhole | |
| // inserting sign change when needed. | |
| if (n < 0 && lastpolarity > 0) { | |
| lastpolarity = -1 | |
| answer.push(minus) | |
| } | |
| else if (n > 0 && lastpolarity < 0) { | |
| lastpolarity = 1 | |
| answer.push(plus) | |
| } | |
| // since we have dealt with sign. let's absolute the value. | |
| absn = Math.abs(n) | |
| // adding number to list We convert these to Hex before storing on the string. | |
| if (absn >= bitness) { | |
| answer.push(remapTailChars(absn.toString(bitness))) | |
| } else { | |
| answer.push(absn.toString(bitness)) | |
| } | |
| } | |
| return answer.join('') | |
| } | |
| , uncompressstrokeleg = function(datastring){ | |
| // we convert half-stroke (only 'x' series or only 'y' series of numbers) | |
| // datastring like this: | |
| // "5agm12100p1235584210m53" | |
| // is converted into this: | |
| // [517,516,514,513,513,513,514,516,519,524,529,537,541,543,544,544,539,536] | |
| // each number in the chain is converted such: | |
| // - digit char = start of new whole number. Alpha chars except "p","m" are numbers in hiding. | |
| // These consecutive digist expressed as alphas mapped back to digit char. | |
| // resurrected number is the diff between this point and prior coord. | |
| // - running polaritiy is attached to the number. | |
| // - we undiff (signed number + prior coord) the number. | |
| // - if char 'm','p', flip running polarity | |
| var answer = [] | |
| , chars = datastring.split('') | |
| , l = chars.length | |
| , ch | |
| , polarity = 1 | |
| , partial = [] | |
| , preprewhole = 0 | |
| , prewhole | |
| for(var i = 0; i < l; i++){ | |
| ch = chars[i] | |
| if (ch in charmap || ch === minus || ch === plus){ | |
| // this is new number - start of a new whole number. | |
| // before we can deal with it, we need to flush out what we already | |
| // parsed out from string, but keep in limbo, waiting for this sign | |
| // that prior number is done. | |
| // we deal with 3 numbers here: | |
| // 1. start of this number - a diff from previous number to | |
| // whole, new number, which we cannot do anything with cause | |
| // we don't know its ending yet. | |
| // 2. number that we now realize have just finished parsing = prewhole | |
| // 3. number we keep around that came before prewhole = preprewhole | |
| if (partial.length !== 0) { | |
| // yep, we have some number parts in there. | |
| prewhole = parseInt( partial.join(''), bitness) * polarity + preprewhole | |
| answer.push( prewhole ) | |
| preprewhole = prewhole | |
| } | |
| if (ch === minus){ | |
| polarity = -1 | |
| partial = [] | |
| } else if (ch === plus){ | |
| polarity = 1 | |
| partial = [] | |
| } else { | |
| // now, let's start collecting parts for the new number: | |
| partial = [ch] | |
| } | |
| } else /* alphas replacing digits */ { | |
| // more parts for the new number | |
| partial.push(charmap_reverse[ch]) | |
| } | |
| } | |
| // we always will have something stuck in partial | |
| // because we don't have closing delimiter | |
| answer.push( parseInt( partial.join(''), bitness ) * polarity + preprewhole ) | |
| return answer | |
| } | |
| , compressstrokes = function(data){ | |
| var answer = [] | |
| , l = data.length | |
| , stroke | |
| for(var i = 0; i < l; i++){ | |
| stroke = data[i] | |
| answer.push(compressstrokeleg(stroke.x)) | |
| answer.push(compressstrokeleg(stroke.y)) | |
| } | |
| return answer.join(chunkSeparator) | |
| } | |
| , uncompressstrokes = function(datastring){ | |
| var data = [] | |
| , chunks = datastring.split(chunkSeparator) | |
| , l = chunks.length / 2 | |
| for (var i = 0; i < l; i++){ | |
| data.push({ | |
| 'x':uncompressstrokeleg(chunks[i*2]) | |
| , 'y':uncompressstrokeleg(chunks[i*2+1]) | |
| }) | |
| } | |
| return data | |
| }; | |
| /* | |
| (c) 2013, Vladimir Agafonkin | |
| Simplify.js, a high-performance JS polyline simplification library | |
| mourner.github.io/simplify-js | |
| */ | |
| //(function () { 'use strict'; | |
| // to suit your point format, run search/replace for '.x' and '.y'; | |
| // for 3D version, see 3d branch (configurability would draw significant performance overhead) | |
| // square distance between 2 points | |
| function getSqDist(p1, p2) { | |
| var dx = p1.x - p2.x, | |
| dy = p1.y - p2.y; | |
| return dx * dx + dy * dy; | |
| } | |
| // square distance from a point to a segment | |
| function getSqSegDist(p, p1, p2) { | |
| var x = p1.x, | |
| y = p1.y, | |
| dx = p2.x - x, | |
| dy = p2.y - y; | |
| if (dx !== 0 || dy !== 0) { | |
| var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | |
| if (t > 1) { | |
| x = p2.x; | |
| y = p2.y; | |
| } else if (t > 0) { | |
| x += dx * t; | |
| y += dy * t; | |
| } | |
| } | |
| dx = p.x - x; | |
| dy = p.y - y; | |
| return dx * dx + dy * dy; | |
| } | |
| // rest of the code doesn't care about point format | |
| // basic distance-based simplification | |
| function simplifyRadialDist(points, sqTolerance) { | |
| var prevPoint = points[0], | |
| newPoints = [prevPoint], | |
| point; | |
| for (var i = 1, len = points.length; i < len; i++) { | |
| point = points[i]; | |
| if (getSqDist(point, prevPoint) > sqTolerance) { | |
| newPoints.push(point); | |
| prevPoint = point; | |
| } | |
| } | |
| if (prevPoint !== point) newPoints.push(point); | |
| return newPoints; | |
| } | |
| function simplifyDPStep(points, first, last, sqTolerance, simplified) { | |
| var maxSqDist = sqTolerance, | |
| index; | |
| for (var i = first + 1; i < last; i++) { | |
| var sqDist = getSqSegDist(points[i], points[first], points[last]); | |
| if (sqDist > maxSqDist) { | |
| index = i; | |
| maxSqDist = sqDist; | |
| } | |
| } | |
| if (maxSqDist > sqTolerance) { | |
| if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified); | |
| simplified.push(points[index]); | |
| if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified); | |
| } | |
| } | |
| // simplification using Ramer-Douglas-Peucker algorithm | |
| function simplifyDouglasPeucker(points, sqTolerance) { | |
| var last = points.length - 1; | |
| var simplified = [points[0]]; | |
| simplifyDPStep(points, 0, last, sqTolerance, simplified); | |
| simplified.push(points[last]); | |
| return simplified; | |
| } | |
| // both algorithms combined for awesome performance | |
| function simplify(points, tolerance, highestQuality) { | |
| if (points.length <= 2) return points; | |
| var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1; | |
| points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); | |
| points = simplifyDouglasPeucker(points, sqTolerance); | |
| return points; | |
| } | |
| // export as AMD module / Node module / browser or worker variable | |
| // if (typeof define === 'function' && define.amd) define(function() { return simplify; }); | |
| // else if (typeof module !== 'undefined') module.exports = simplify; | |
| // else if (typeof self !== 'undefined') self.simplify = simplify; | |
| // else window.simplify = simplify; | |
| //})(); | |
| /** | |
| Vector class. Allows us to simplify representation and manipulation of coordinate-pair | |
| representing shift against (0, 0) | |
| @public | |
| @class | |
| @param | |
| @returns {Type} | |
| */ | |
| function Vector(x,y){ | |
| this.x = x | |
| this.y = y | |
| this.reverse = function(){ | |
| return new this.constructor( | |
| this.x * -1 | |
| , this.y * -1 | |
| ) | |
| } | |
| this._length = null | |
| this.getLength = function(){ | |
| if (!this._length){ | |
| this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) ) | |
| } | |
| return this._length | |
| } | |
| var polarity = function (e){ | |
| return Math.round(e / Math.abs(e)) | |
| } | |
| this.resizeTo = function(length){ | |
| // proportionally changes x,y such that the hypotenuse (vector length) is = new length | |
| if (this.x === 0 && this.y === 0){ | |
| this._length = 0 | |
| } else if (this.x === 0){ | |
| this._length = length | |
| this.y = length * polarity(this.y) | |
| } else if(this.y === 0){ | |
| this._length = length | |
| this.x = length * polarity(this.x) | |
| } else { | |
| var proportion = Math.abs(this.y / this.x) | |
| , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2))) | |
| , y = proportion * x | |
| this._length = length | |
| this.x = x * polarity(this.x) | |
| this.y = y * polarity(this.y) | |
| } | |
| return this | |
| } | |
| /** | |
| * Calculates the angle between 'this' vector and another. | |
| * @public | |
| * @function | |
| * @returns {Number} The angle between the two vectors as measured in PI. | |
| */ | |
| this.angleTo = function(vectorB) { | |
| var divisor = this.getLength() * vectorB.getLength() | |
| if (divisor === 0) { | |
| return 0 | |
| } else { | |
| // JavaScript floating point math is screwed up. | |
| // because of it, the core of the formula can, on occasion, have values | |
| // over 1.0 and below -1.0. | |
| return Math.acos( | |
| Math.min( | |
| Math.max( | |
| ( this.x * vectorB.x + this.y * vectorB.y ) / divisor | |
| , -1.0 | |
| ) | |
| , 1.0 | |
| ) | |
| ) / Math.PI | |
| } | |
| } | |
| } | |
| function Point(x,y){ | |
| this.x = x | |
| this.y = y | |
| this.getVectorToCoordinates = function (x, y) { | |
| return new Vector(x - this.x, y - this.y) | |
| } | |
| this.getVectorFromCoordinates = function (x, y) { | |
| return this.getVectorToCoordinates(x, y).reverse() | |
| } | |
| this.getVectorToPoint = function (point) { | |
| return new Vector(point.x - this.x, point.y - this.y) | |
| } | |
| this.getVectorFromPoint = function (point) { | |
| return this.getVectorToPoint(point).reverse() | |
| } | |
| } | |
| /** | |
| Allows one to round a number to arbitrary precision. | |
| Math.round() rounds to whole only. | |
| Number.toFixed(precision) returns a string. | |
| I need float to float, but with arbitrary precision, hence: | |
| @public | |
| @function | |
| @param number {Number} | |
| @param position {Number} number of digits right of decimal point to keep. If negative, rounding to the left of decimal. | |
| @returns {Type} | |
| */ | |
| function round (number, position){ | |
| var tmp = Math.pow(10, position) | |
| return Math.round( number * tmp ) / tmp | |
| } | |
| // /** | |
| // * This is a simple, points-to-lines (not curves) renderer. | |
| // * Keeping it around so we can activate it from time to time and see | |
| // * if smoothing logic is off much. | |
| // * @public | |
| // * @function | |
| // * @returns {String} Like so "l 1 2 3 5' with stroke as long line chain. | |
| // */ | |
| // function compressstroke(stroke, shiftx, shifty){ | |
| // // we combine strokes data into string like this: | |
| // // 'M 53 7 l 1 2 3 4 -5 -6 5 -6' | |
| // // see SVG documentation for Path element's 'd' argument. | |
| // var lastx = stroke.x[0] | |
| // , lasty = stroke.y[0] | |
| // , i | |
| // , l = stroke.x.length | |
| // , answer = ['M', lastx - shiftx, lasty - shifty, 'l'] | |
| // | |
| // if (l === 1){ | |
| // // meaning this was just a DOT, not a stroke. | |
| // // instead of creating a circle, we just create a short line | |
| // answer.concat(1, -1) | |
| // } else { | |
| // for(i = 1; i < l; i++){ | |
| // answer = answer.concat(stroke.x[i] - lastx, stroke.y[i] - lasty) | |
| // lastx = stroke.x[i] | |
| // lasty = stroke.y[i] | |
| // } | |
| // } | |
| // return answer.join(' ') | |
| // } | |
| function segmentToCurve(stroke, positionInStroke, lineCurveThreshold){ | |
| 'use strict' | |
| // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. | |
| // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! | |
| // We want to approximate pretty curves in-place of those ugly lines. | |
| // To approximate a very nice curve we need to know the direction of line before and after. | |
| // Hence, on long lines we actually wait for another point beyond it to come back from | |
| // mousemoved before we draw this curve. | |
| // So for "prior curve" to be calc'ed we need 4 points | |
| // A, B, C, D (we are on D now, A is 3 points in the past.) | |
| // and 3 lines: | |
| // pre-line (from points A to B), | |
| // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) | |
| // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) | |
| // | |
| // Well, actually, we don't need to *know* the point A, just the vector A->B | |
| // Again, we can only derive curve between points positionInStroke-1 and positionInStroke | |
| // Thus, since we can only draw a line if we know one point ahead of it, we need to shift our focus one point ahead. | |
| positionInStroke += 1 | |
| // Let's hope the code that calls us knows we do that and does not call us with positionInStroke = index of last point. | |
| var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) | |
| , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) | |
| , CDvector = Cpoint.getVectorToPoint(Dpoint) | |
| // Again, we have a chance here to draw only PREVIOUS line segment - BC | |
| // So, let's start with BC curve. | |
| // if there is only 2 points in stroke array (C, D), we don't have "history" long enough to have point B, let alone point A. | |
| // so positionInStroke should start with 2, ie | |
| // we are here when there are at least 3 points in stroke array. | |
| var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2]) | |
| , BCvector = Bpoint.getVectorToPoint(Cpoint) | |
| , ABvector | |
| , rounding = 2 | |
| if ( BCvector.getLength() > lineCurveThreshold ){ | |
| // Yey! Pretty curves, here we come! | |
| if(positionInStroke > 2) { | |
| ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint) | |
| } else { | |
| ABvector = new Vector(0,0) | |
| } | |
| var minlenfraction = 0.05 | |
| , maxlen = BCvector.getLength() * 0.35 | |
| , ABCangle = BCvector.angleTo(ABvector.reverse()) | |
| , BCDangle = CDvector.angleTo(BCvector.reverse()) | |
| , BtoCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo( | |
| Math.max(minlenfraction, ABCangle) * maxlen | |
| ) | |
| , CtoCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo( | |
| Math.max(minlenfraction, BCDangle) * maxlen | |
| ) | |
| , BtoCP2vector = new Vector(BCvector.x + CtoCP2vector.x, BCvector.y + CtoCP2vector.y) | |
| // returing curve for BC segment | |
| // all coords are vectors against Bpoint | |
| return [ | |
| 'c' // bezier curve | |
| , round( BtoCP1vector.x, rounding ) | |
| , round( BtoCP1vector.y, rounding ) | |
| , round( BtoCP2vector.x, rounding ) | |
| , round( BtoCP2vector.y, rounding ) | |
| , round( BCvector.x, rounding ) | |
| , round( BCvector.y, rounding ) | |
| ] | |
| } else { | |
| return [ | |
| 'l' // line | |
| , round( BCvector.x, rounding ) | |
| , round( BCvector.y, rounding ) | |
| ] | |
| } | |
| } | |
| function lastSegmentToCurve(stroke, lineCurveThreshold){ | |
| 'use strict' | |
| // Here we tidy up things left unfinished | |
| // What's left unfinished there is the curve between the last points | |
| // in the stroke | |
| // We can also be called when there is only one point in the stroke (meaning, the | |
| // stroke was just a dot), in which case there is nothing for us to do. | |
| // So for "this curve" to be calc'ed we need 3 points | |
| // A, B, C | |
| // and 2 lines: | |
| // pre-line (from points A to B), | |
| // this line (from points B to C) | |
| // Well, actually, we don't need to *know* the point A, just the vector A->B | |
| // so, we really need points B, C and AB vector. | |
| var positionInStroke = stroke.x.length - 1 | |
| // there must be at least 2 points in the stroke.for us to work. Hope calling code checks for that. | |
| var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) | |
| , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) | |
| , BCvector = Bpoint.getVectorToPoint(Cpoint) | |
| , rounding = 2 | |
| if (positionInStroke > 1 && BCvector.getLength() > lineCurveThreshold){ | |
| // we have at least 3 elems in stroke | |
| var ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint) | |
| , ABCangle = BCvector.angleTo(ABvector.reverse()) | |
| , minlenfraction = 0.05 | |
| , maxlen = BCvector.getLength() * 0.35 | |
| , BtoCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo( | |
| Math.max(minlenfraction, ABCangle) * maxlen | |
| ) | |
| return [ | |
| 'c' // bezier curve | |
| , round( BtoCP1vector.x, rounding ) | |
| , round( BtoCP1vector.y, rounding ) | |
| , round( BCvector.x, rounding ) // CP2 is same as Cpoint | |
| , round( BCvector.y, rounding ) // CP2 is same as Cpoint | |
| , round( BCvector.x, rounding ) | |
| , round( BCvector.y, rounding ) | |
| ] | |
| } else { | |
| // Since there is no AB leg, there is no curve to draw. This is just line | |
| return [ | |
| 'l' // simple line | |
| , round( BCvector.x, rounding ) | |
| , round( BCvector.y, rounding ) | |
| ] | |
| } | |
| } | |
| function addstroke(stroke, shiftx, shifty){ | |
| 'use strict' | |
| // we combine strokes data into string like this: | |
| // 'M 53 7 l 1 2 c 3 4 -5 -6 5 -6' | |
| // see SVG documentation for Path element's 'd' argument. | |
| var lines = [ | |
| 'M' // move to | |
| , round( (stroke.x[0] - shiftx), 2) | |
| , round( (stroke.y[0] - shifty), 2) | |
| ] | |
| // processing all points but first and last. | |
| , i = 1 // index zero item in there is STARTING point. we already extracted it. | |
| , l = stroke.x.length - 1 // this is a trick. We are leaving last point coordinates for separate processing. | |
| , lineCurveThreshold = 1 | |
| for(; i < l; i++){ | |
| lines.push.apply(lines, segmentToCurve(stroke, i, lineCurveThreshold)) | |
| } | |
| if (l > 0 /* effectively more than 1, since we "-1" above */){ | |
| lines.push.apply(lines, lastSegmentToCurve(stroke, i, lineCurveThreshold)) | |
| } else if (l === 0){ | |
| // meaning we only have ONE point in the stroke (and otherwise refer to the stroke as "dot") | |
| lines.push.apply(lines, ['l' , 1, 1]) | |
| } | |
| return lines.join(' ') | |
| } | |
| function simplifystroke(stroke){ | |
| var d = [] | |
| , newstroke = {'x':[], 'y':[]} | |
| , i, l | |
| for (i = 0, l = stroke.x.length; i < l; i++){ | |
| d.push({'x':stroke.x[i], 'y':stroke.y[i]}) | |
| } | |
| d = simplify(d, 0.7, true) | |
| for (i = 0, l = d.length; i < l; i++){ | |
| newstroke.x.push(d[i].x) | |
| newstroke.y.push(d[i].y) | |
| } | |
| return newstroke | |
| } | |
| // generate SVG style from settings | |
| function styleFromSettings(settings){ | |
| var styles = []; | |
| var meta = [ | |
| // ["style attr", "key in settings", "default value"] | |
| ["fill", undefined, "none"], | |
| ["stroke", "color", "#000000"], | |
| ["stroke-width", "lineWidth", 2], | |
| ["stroke-linecap", undefined, "round"], | |
| ["stroke-linejoin", undefined, "round"] | |
| ]; | |
| for (var i = meta.length - 1; i >= 0; i--){ | |
| var attr = meta[i][0] | |
| , key = meta[i][1] | |
| , defaultVal = meta[i][2]; | |
| styles.push(attr + '="' + (key in settings && settings[key] ? settings[key] : defaultVal) + '"'); | |
| } | |
| return styles.join(' '); | |
| } | |
| function compressstrokesSVG(data, settings){ | |
| 'use strict' | |
| var answer = [ | |
| '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' | |
| , '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' | |
| ] | |
| , i , l = data.length | |
| , stroke | |
| , xlimits = [] | |
| , ylimits = [] | |
| , sizex = 0 | |
| , sizey = 0 | |
| , shiftx = 0 | |
| , shifty = 0 | |
| , minx, maxx, miny, maxy, padding = 1 | |
| , simplifieddata = [] | |
| if(l !== 0){ | |
| for(i = 0; i < l; i++){ | |
| stroke = simplifystroke( data[i] ) | |
| simplifieddata.push(stroke) | |
| xlimits = xlimits.concat(stroke.x) | |
| ylimits = ylimits.concat(stroke.y) | |
| } | |
| minx = Math.min.apply(null, xlimits) - padding | |
| maxx = Math.max.apply(null, xlimits) + padding | |
| miny = Math.min.apply(null, ylimits) - padding | |
| maxy = Math.max.apply(null, ylimits) + padding | |
| shiftx = minx < 0? 0 : minx | |
| shifty = miny < 0? 0 : miny | |
| sizex = maxx - minx | |
| sizey = maxy - miny | |
| } | |
| answer.push( | |
| '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+ | |
| sizex.toString() + | |
| '" height="'+ | |
| sizey.toString() + | |
| '">' | |
| ) | |
| // // This is a nice idea: use style declaration on top, and mark the lines with 'class="f"' | |
| // // thus saving space in svg... | |
| // // alas, many SVG renderers don't understand "class" and render the strokes in default "fill = black, no stroke" style. Ugh!!! | |
| // // TODO: Rewrite ImageMagic / GraphicsMagic, InkScape, http://svg.codeplex.com/ to support style + class. until then, we hardcode the stroke style within the path. | |
| // answer.push( | |
| // '<style type="text/css"><![CDATA[.f {fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}]]></style>' | |
| // ) | |
| // // This set is accompaniment to "simple line renderer" - compressstroke | |
| // answer.push( | |
| // '<style type="text/css"><![CDATA[.t {fill:none;stroke:#FF0000;stroke-width:2}]]></style>' | |
| // ) | |
| // for(i = 0; i < l; i++){ | |
| // stroke = data[i] | |
| // // This one is accompaniment to "simple line renderer" | |
| // answer.push('<path class="t" d="'+ compressstroke(stroke, shiftx, shifty) +'"/>') | |
| // } | |
| for(i = 0, l = simplifieddata.length; i < l; i++){ | |
| stroke = simplifieddata[i] | |
| answer.push('<path ' + styleFromSettings(settings) + ' d="'+ addstroke(stroke, shiftx, shifty) + '"/>') | |
| } | |
| answer.push('</svg>') | |
| return answer.join('') | |
| } | |
| function compressstrokesPaths(data, settings){ | |
| 'use strict' | |
| var answer = {paths:[]} | |
| , i , l = data.length | |
| , stroke | |
| , xlimits = [] | |
| , ylimits = [] | |
| , sizex = 0 | |
| , sizey = 0 | |
| , shiftx = 0 | |
| , shifty = 0 | |
| , minx, maxx, miny, maxy, padding = 1 | |
| , simplifieddata = [] | |
| if(l !== 0){ | |
| for(i = 0; i < l; i++){ | |
| stroke = simplifystroke( data[i] ) | |
| simplifieddata.push(stroke) | |
| xlimits = xlimits.concat(stroke.x) | |
| ylimits = ylimits.concat(stroke.y) | |
| } | |
| minx = Math.min.apply(null, xlimits) - padding | |
| maxx = Math.max.apply(null, xlimits) + padding | |
| miny = Math.min.apply(null, ylimits) - padding | |
| maxy = Math.max.apply(null, ylimits) + padding | |
| shiftx = minx < 0? 0 : minx | |
| shifty = miny < 0? 0 : miny | |
| sizex = maxx - minx | |
| sizey = maxy - miny | |
| } | |
| answer.width=sizex; | |
| answer.height=sizey; | |
| for(i = 0, l = simplifieddata.length; i < l; i++){ | |
| stroke = simplifieddata[i] | |
| answer.paths.push(addstroke(stroke, shiftx, shifty)) | |
| } | |
| return answer | |
| } | |
| if (typeof btoa !== 'function') | |
| { | |
| var btoa = function(data) { | |
| /** @preserve | |
| base64 encoder | |
| MIT, GPL | |
| http://phpjs.org/functions/base64_encode | |
| + original by: Tyler Akins (http://rumkin.com) | |
| + improved by: Bayron Guevara | |
| + improved by: Thunder.m | |
| + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) | |
| + bugfixed by: Pellentesque Malesuada | |
| + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) | |
| + improved by: Rafal Kukawski (http://kukawski.pl) | |
| */ | |
| var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" | |
| , b64a = b64.split('') | |
| , o1, o2, o3, h1, h2, h3, h4, bits, i = 0, | |
| ac = 0, | |
| enc = "", | |
| tmp_arr = []; | |
| do { // pack three octets into four hexets | |
| o1 = data.charCodeAt(i++); | |
| o2 = data.charCodeAt(i++); | |
| o3 = data.charCodeAt(i++); | |
| bits = o1 << 16 | o2 << 8 | o3; | |
| h1 = bits >> 18 & 0x3f; | |
| h2 = bits >> 12 & 0x3f; | |
| h3 = bits >> 6 & 0x3f; | |
| h4 = bits & 0x3f; | |
| // use hexets to index into b64, and append result to encoded string | |
| tmp_arr[ac++] = b64a[h1] + b64a[h2] + b64a[h3] + b64a[h4]; | |
| } while (i < data.length); | |
| enc = tmp_arr.join(''); | |
| var r = data.length % 3; | |
| return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); | |
| // end of base64 encoder MIT, GPL | |
| } | |
| } | |
| var unencodedmime = 'image/svg+xml' | |
| function getUnencodedSVG(data, settings){ | |
| return [unencodedmime , compressstrokesSVG(data, settings)]; | |
| } | |
| var base64encodedmime = 'image/svg+xml;base64' | |
| function getBase64encodedSVG(data, settings){ | |
| return [base64encodedmime , btoa( compressstrokesSVG(data, settings) )]; | |
| } | |
| function base30toSVG(data, settings){ | |
| var native = uncompressstrokes(data); | |
| var _settings = { | |
| color: "rgb(0, 0, 0)", | |
| lineWidth: 2 | |
| }; | |
| if (settings && settings.color) _settings.color = settings.color; | |
| if (settings && settings.lineWidth) _settings.lineWidth = settings.lineWidth; | |
| return getUnencodedSVG(native, _settings); | |
| } | |
| function base30toPaths(data){ | |
| var native = uncompressstrokes(data); | |
| return compressstrokesPaths(native); | |
| } | |
| return { | |
| base30toSVG: base30toSVG, | |
| base30toPaths: base30toPaths | |
| } | |
| }()); | 
Hello there,
I've created repository with this helper, allowing installation via bower or npm:
https://github.com/OldrichKruchna/jSignatureHelper
Please feel free to write anything what is missing by your opinion.
@mattgaspar im not sure that your helper converts correctly because when i try to visualize it as image or as background of div it shows nothing, as well there are NaNs|undefined parameters in decoded svg representation
The decoded base30 was generated by jSignature and it was imported and rerendered by jSignature back without errors
the code is here
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1699" height="NaN"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="2" stroke="rgb(0, 0, 0)" fill="none" d="M 1 NaN c 0.18 -0.05 6.58 -2.39 10 -3 c 9.59 -1.72 19.74 -2.1 29 -4 c 3.41 -0.7 6.52 -2.96 10 -4 c 13.41 -4.02 28.18 -6.51 40 -11 c 3.65 -1.39 6.44 -5.67 10 -8 c 5.1 -3.33 10.92 -5.61 16 -9 c 4.93 -3.29 9.05 -7.7 14 -11 c 6.11 -4.07 12.35 -7.46 19 -11 c 9.49 -5.05 18.98 -8.62 28 -14 c 11.82 -7.06 22.3 -16.22 34 -23 c 7.15 -4.14 15.94 -6.15 23 -10 c 3.6 -1.97 6.47 -7.94 10 -8 c 239.74 -3.81 572 -3.34 870 -6 c 9.27 -0.08 18.23 -0.72 27 -2 c 4.71 -0.69 10.21 -4.76 14 -4 c 6.17 1.23 13.69 7.72 21 11 c 9.27 4.16 21.11 5.91 28 11 c 5.59 4.13 11.28 13.28 14 20 c 2.47 6.11 2.49 14.58 3 22 c 0.5 7.35 -3.97 21.5 0 22 c 63.15 7.96 295.22 16.08 349 22 c 2.54 0.28 -2.32 10.77 -4 16 c -1.33 4.13 -2.83 8.66 -5 12 c -1.89 2.91 -5.14 5.98 -8 8 c -2.49 1.76 -6.06 3.47 -9 4 c -3.88 0.71 -8.64 -0.3 -13 0 c -5.41 0.37 -10.61 1.93 -16 2 c -20.39 0.26 -40.52 0.38 -61 -1 c -14.57 -0.98 -28.33 -3.63 -43 -6 c -6.56 -1.06 -13.03 -2.01 -19 -4 c -5.78 -1.93 -11.8 -4.81 -17 -8 c -4.93 -3.02 -10.26 -6.92 -14 -11 c -3.2 -3.5 -5.66 -8.48 -8 -13 c -2.34 -4.51 -4.28 -9.18 -6 -14 c -1.64 -4.61 -4 -9.81 -4 -14 c 0 -4.19 2.07 -9.83 4 -14 c 1.91 -4.13 4.86 -8.6 8 -12 c 4.62 -5.01 10.22 -10.73 16 -14 c 8.78 -4.97 19.57 -8.21 30 -12 c 12.25 -4.45 23.48 -8.15 36 -12 c 14.41 -4.43 27.46 -8.28 42 -12 c 13.65 -3.5 26.12 -6.39 40 -9 c 33.67 -6.34 64.31 -12.03 98 -17 c 19.75 -2.91 41.27 -6 58 -6 c 3.85 0 9.15 3.49 12 6 c 2.19 1.93 3.56 5.86 5 9 c 2.26 4.93 5.61 10.3 6 15 c 0.51 6.17 -1.41 14.15 -3 21 c -1.73 7.45 -4.07 14.9 -7 22 c -3.41 8.25 -7.16 16.91 -12 24 c -4.85 7.11 -11.41 14.14 -18 20 c -8.23 7.31 -17.52 14.21 -27 20 c -10.1 6.16 -20.93 11.3 -32 16 c -11.16 4.73 -22.29 8.45 -34 12 c -10.72 3.25 -21.05 5.81 -32 8 c -9.39 1.88 NaN NaN -28 4 l -212 NaN"/></svg>undefined
To use call: jSigHelper.base30toSVG(base30sig)
Also added a function to get path data in array (used for drawing signature in PDF)
jSigHelper.base30toPaths(base30sig)