/** @license jSignature v2 SVG export plugin. */ /** Copyright (c) 2012 Willow Systems Corp http://willow-systems.com MIT License */ ;(function(){ 'use strict' /** @preserve Simplify.js BSD (c) 2012, Vladimir Agafonkin mourner.github.com/simplify-js */ ;(function(a,b){function c(a,b){var c=a.x-b.x,d=a.y-b.y;return c*c+d*d}function d(a,b,c){var d=b.x,e=b.y,f=c.x-d,g=c.y-e,h;if(f!==0||g!==0)h=((a.x-d)*f+(a.y-e)*g)/(f*f+g*g),h>1?(d=c.x,e=c.y):h>0&&(d+=f*h,e+=g*h);return f=a.x-d,g=a.y-e,f*f+g*g}function e(a,b){var d,e=a.length,f,g=a[0],h=[g];for(d=1;db&&(h.push(f),g=f);return g!==f&&h.push(f),h}function f(a,c){var e=a.length,f=typeof Uint8Array!=b+""?Uint8Array:Array,g=new f(e),h=0,i=e-1,j,k,l,m,n=[],o=[],p=[];g[h]=g[i]=1;while(i){k=0;for(j=h+1;jk&&(m=j,k=l);k>c&&(g[m]=1,n.push(h),o.push(m),n.push(m),o.push(i)),h=n.pop(),i=o.pop()}for(j=0;jB // 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 compressstrokes(data, settings){ 'use strict' var answer = [ '' , '' ] , 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( '' ) // // 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( // '' // ) // // This set is accompaniment to "simple line renderer" - compressstroke // answer.push( // '' // ) // for(i = 0; i < l; i++){ // stroke = data[i] // // This one is accompaniment to "simple line renderer" // answer.push('') // } for(i = 0, l = simplifieddata.length; i < l; i++){ stroke = simplifieddata[i] answer.push('') } answer.push('') return answer.join('') } 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 , compressstrokes(data, settings)]; } var base64encodedmime = 'image/svg+xml;base64' function getBase64encodedSVG(data, settings){ return [base64encodedmime , btoa( compressstrokes(data, settings) )]; } function Initializer($){ var mothership = $.fn['jSignature'] mothership( 'addPlugin' ,'export' ,'svg' // alias ,getUnencodedSVG ) mothership( 'addPlugin' ,'export' ,unencodedmime // full name ,getUnencodedSVG ) mothership( 'addPlugin' ,'export' ,'svgbase64' // alias ,getBase64encodedSVG ) mothership( 'addPlugin' ,'export' ,base64encodedmime // full name ,getBase64encodedSVG ) } // //Because plugins are minified together with jSignature, multiple defines per (minified) file blow up and dont make sense // //Need to revisit this later. if(typeof $ === 'undefined') {throw new Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...")} Initializer($) })();