using System;
using System.Collections.Generic;
using System.Text;
namespace jSignature.Tools
{
///
/// This class Converts jSignature data into compressed alphanum base30 string and back.
///
public class Base30Converter
{
///
/// These chars' place numbers correspond to the number they represent.
///
string ALLCHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX";
int bitness;
char MINUS = 'Z';
char PLUS = 'Y';
Dictionary charmap;
Dictionary charmap_tail;
public Base30Converter(){
bitness = ALLCHARS.Length / 2; // will equal 30
charmap = new Dictionary();
charmap_tail = new Dictionary();
for (int i = 0; i < bitness; i++)
{
charmap.Add(ALLCHARS[i], i);
charmap_tail.Add(ALLCHARS[i+bitness], i);
}
}
private int FromBase30(List data)
{
int len = data.Count;
if (len == 1)
{
return data[0];
}
else
{
data.Reverse();
// now we know that we have at least 2 elems.
double answer = data[0] + data[1] * bitness;
for (int i = 2; i < len; i++)
{
answer = answer + data[i] * Math.Pow(bitness, i);
}
return (int)answer;
}
}
private List ToBase30(int val)
{
List retVal = new List();
//Determine the max power of bitness
int pow = 0;
while (Math.Pow(bitness, pow + 1) < val) pow++;
//Iterate through each of the powers of the bitness to
int num = val;
while (pow >= 0)
{
int mag = Convert.ToInt32(Math.Pow(bitness, pow));
int div = num / mag;
retVal.Add(div);
num = num - (div * mag);
pow--;
}
return retVal;
}
private string CompressStrokeLeg(int[] val)
{
StringBuilder sb = new StringBuilder();
char polarity = PLUS;
foreach (int num in val)
{
//Put the number into base30 format
List cell = ToBase30(Math.Abs(num));
//If the polarity has changed, then we need to lay down a polarity indicator
char newpolarity;
if (num == 0) newpolarity = polarity; //Zero indicates no change in polarity
else newpolarity = (num >= 0) ? PLUS : MINUS;
if (newpolarity != polarity)
{
sb.Append(newpolarity);
polarity = newpolarity;
}
//Now convert into the jSignature Base30 compressed format
for (int i = 0; i < cell.Count; i++)
{
//The first "bitness" characters in the character set are used for the first member of the
//number representation. If the number representation has more than 1 character, the
//offset in the character set is shifted by "bitness"
int charsetoffset = (i > 0) ? bitness : 0;
sb.Append(ALLCHARS[cell[i] + charsetoffset]);
}
}
return sb.ToString();
}
public int[] DecompressStrokeLeg(string data)
{
List leg = new List();
List cell = new List();
int polarity = 1;
foreach (char c in data)
{
if (charmap_tail.ContainsKey(c))
{
// this is a char that indicates continuation of a number that started a earlier number.
cell.Add(charmap_tail[c]);
}
else
{
// This is a start of new number (or, in case of + or - an end of previous number)
// We can now convert the parts we piled up in cell array into an int.
if (cell.Count != 0) {
// yep, we have some number parts in there.
leg.Add(FromBase30(cell) * polarity);
}
// When i say "we start a new number" I mean it!
cell.Clear();
if (c == MINUS){
polarity = -1;
} else if (c == PLUS){
polarity = 1;
} else {
// now, let's start collecting parts for the new number:
cell.Add(charmap[c]);
}
}
}
// we will alway have one number stuck in cell array because no "new number starts" follows it.
leg.Add(FromBase30(cell) * polarity);
return leg.ToArray();
}
private int[][] GetStroke(string legX, string legY)
{
// Examples of legX, legY: "7UZ32232263353223222333242", "3w546647c9b96646475765444"
var X = DecompressStrokeLeg(legX);
var Y = DecompressStrokeLeg(legY);
int len = X.Length;
if (len != Y.Length)
{
throw new Exception("Coordinate length for Y side of the stroke does not match the coordinate length of X side of the stroke");
}
List l = new List();
for (int i = 0; i < len; i++)
{
l.Add(new int[] {X[i], Y[i]});
}
return l.ToArray();
}
///
/// Returns a .net-specific array of arrays structure representing a single signature stroke
/// A compressed string like this one:
/// "3E13Z5Y5_1O24Z66_1O1Z3_3E2Z4"
/// representing this raw signature data:
/// [{'x':[100,101,104,99,104],'y':[50,52,56,50,44]},{'x':[50,51,48],'y':[100,102,98]}]
/// turns into this .Net-specific structure (of array or arrays of arrays)
/// [[[100,50],[1,2],[3,4],[-5,-6],[5,-6]], [[50,100],[1,2],[-3,-4]]]
///
/// string of data encoded in base30 format. Ex: "3E13Z5Y5_1O24Z66_1O1Z3_3E2Z4"
///
public int[][][] Base30ToNative(string data){
List ss = new List();
string[] parts = data.Split('_');
int len = parts.Length / 2;
for (int i = 0; i < len; i++)
{
ss.Add(GetStroke(
parts[i * 2]
, parts[i * 2 + 1]
));
}
return ss.ToArray();
}
///
/// Returns a compressed string like this one:
/// "3E13Z5Y5_1O24Z66_1O1Z3_3E2Z4"
/// Originating from a stroke list such as:
/// [[[100,50],[1,2],[3,4],[-5,-6],[5,-6]], [[50,100],[1,2],[-3,-4]]]
///
/// .Net-specific structure (of array or arrays of arrays)
///
public string NativeToBase30(int[][][] data)
{
StringBuilder sb = new StringBuilder();
List LegX = new List();
List LegY = new List();
foreach (int[][] stroke in data)
{
LegX.Clear();
LegY.Clear();
foreach (int[] line in stroke)
{
if (line.Length != 2) throw new Exception("Invalid coordinate");
LegX.Add(line[0]);
LegY.Add(line[1]);
}
if (sb.Length > 0) sb.Append("_");
sb.Append(CompressStrokeLeg(LegX.ToArray()));
sb.Append("_");
sb.Append(CompressStrokeLeg(LegY.ToArray()));
}
return sb.ToString();
}
}
}