Skip to content

Instantly share code, notes, and snippets.

@mattgaspar
Created November 12, 2015 08:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mattgaspar/170e00731711e8bb94e1 to your computer and use it in GitHub Desktop.
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
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
}
}());
@mattgaspar
Copy link
Author

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)

@OldrichKruchna
Copy link

OldrichKruchna commented Oct 18, 2016

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.

@nikonov91-dev
Copy link

nikonov91-dev commented Jan 17, 2020

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment