Created
November 12, 2015 08:15
-
-
Save mattgaspar/170e00731711e8bb94e1 to your computer and use it in GitHub Desktop.
jSignature helper - Code pulled from jSignature plugins for use in server side javascript to convert base30 to SVG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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