/*======================================================
 * Scriptel ScripTouch EasyScript JavaScript API
 * Copyright 2013 - Scriptel Corporation
 *======================================================
 */

Date.now = Date.now || function () {"use strict"; return new Date().getTime(); };

/**
 * Constructor, creates a new instance of this type.
 * @classdesc This error is thrown in the event there is an error parsing the
 * signature.
 * @constructor
 */


/* global InternationalKeyboard, InstallTrigger */

function SignatureError(msg, pos) {
    "use strict";
    /**
     * This member carries a human readable message as to what type of error
     * occurred while parsing.
     * @public
     * @instance
     * @type {string}
     */
    this.message = msg;
    /**
     * This member carries the character position of where the error occurred.
     * @public
     * @instance
     * @type {number}
     */
    this.position = pos;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a parsed signature string.
 * @constructor
 */
function ScriptelSignature() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelSignature";
    /**
     * The captured protocol version, at present 'A, B, C , D' exists.
     * @public
     * @instance
     * @type {string}
     */
    this.protocolVersion = "";
    /**
     * The model of the device that captured the signature.
     * @public
     * @instance
     * @type {string}
     */
    this.model = "";
    /**
     * The currently running version of the firmware on the device that captured the signature.
     * @public
     * @instance
     * @type {string}
     */
    this.version = "";
    /**
     * An array of coordinate arrays containing the pen strokes parsed from the signature stream.
     * @public
     * @instance
     * @type {array<array<ScriptelCoordinate>>
     */
    this.strokes = [];
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a parsed card swipe string.
 * @constructor
 */
function ScriptelCardSwipe() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelCardSwipe";
    /**
     * The captured protocol version, at present 'A, B, C, D' exists.
     * @public
     * @instance
     * @type {string}
     */
    this.protocolVersion = "";
    /**
     * This will contain the captured card swipe data. Any tracks
     * that were unable to be read will not be present.
     */
    this.cardData = "";
    /**
     * If this card is a financial card, parsed information will
     * be available here.
     */
    this.financialCard = false;
    /**
     * If this card is an identification card (driver's license)
     * the parsed information will be available here.
     */
    this.identificationCard = false;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a parsed financial card.
 * @constructor
 */
function ScriptelFinancialCard() {
    "use strict";
    /**
     * The type name
     */
    this.type =  "ScriptelFinancialCard";
    /**
     * Contains the contents of the parsed first track. False if the track couldn't be read/parsed.
     * @public
     * @instance
     * @type {FinancialTrackOneData}
     */
    this.trackOne = false;
    /**
     * Contains the contents of the parsed second track. False if the track couldn't be read/parsed.
     * @public
     * @instance
     * @type {FinancialTrackTwoData}
     */
    this.trackTwo = false;
    /**
     * Contains the name of the card issuer, false if unknown.
     * @public
     * @instance
     * @type {string}
     */
    this.cardIssuer = false;
    /**
     * Indiciates whether or not the identification number on the card passed checksum.
     * @public
     * @instance
     * @type {boolean}
     */
    this.numberValid = false;
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a parsed identification card.
 * @constructor
 */
function ScriptelIdentificationCard() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelIdentificationCard";
    /**
     * Contains the contents of the first parsed track. False if the track couldn't be read/parsed.
     * @public
     * @instance
     * @type {IdentificationTrackOneData}
     */
    this.trackOne = false;
    /**
     * Contains the contents of the second parsed track. False if the track couldn't be read/parsed.
     * @public
     * @instance
     * @type {IdentificationTrackTwoData}
     */
    this.trackTwo = false;
    /**
     * Contains the contents of the third parsed track. False if the track couldn't be read/parsed.
     * @public
     * @instance
     * @type {IdentificationTrackThreeData}
     */
    this.trackThree = false;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a parsed signature string.
 * @constructor
 */
function ScriptelSignatureMetaData() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelSignatureMetaData";
    /**
     * The captured protocol version, at present 'A, B, C , D' exists.
     * @public
     * @instance
     * @type {string}
     */
    this.protocolVersion = "";
    /**
     * The position in the string where protocolVersion starts
     * @public
     * @instance
     * type {int}
     */
    this.protocolVersionPosition = 0;
    /**
     * The model of the device that captured the signature.
     * @public
     * @instance
     * @type {string}
     */
    this.model = "";
    /**
     * The position in the string where model starts.
     * @public
     * @instance
     * @type {int}
     */
    this.modelPosition = 0;
    /**
     * The currently running version of the firmware on the device that captured the signature.
     * @public
     * @instance
     * @type {string}
     */
    this.version = "";
    /**
     * The position in the string where version starts
     * @public
     * @instance
     * @type {int}
     */
    this.versionPosition = 0;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents a single point on a two dimensional plane.
 * @constructor
 */
function ScriptelCoordinate(x, y) {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelCoordinate";
    /**
     * The horizontal location of the point.
     * @public
     * @instance
     * @type {number}
     */
    this.x = x;
    /**
     * The vertical location of the point.
     * @public
     * @instance
     * @type {number}
     */
    this.y = y;
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents that a new stroke has been encountered
 * @constructor
 */
function ScriptelNewStroke() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelNewStroke";
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the signature leading up to this has been canceled
 * @constructor
 */
function ScriptelCancelSignature() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelCancelSignature";
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents that signature leading up to this is complete
 * @constructor
 */
function ScriptelSignatureComplete() {
    "use strict";
    /**
     * The type name
     */
    this.type = "ScriptelSignatureComplete";
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class returns the portion of a signature string that was just processed.
 * @constructor
 */
function ScriptelOriginalString(fragment) {
    "use strict";
    this.type = "ScriptelOriginalString";
    this.originalStringFragment = fragment;
}
/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the bounding box around a set of points on
 * a two dimensional plane.
 * @constructor
 */
function ScriptelBoundingBox() {
    "use strict";
    /**
     * The left most edge.
     * @public
     * @instance
     * @type {number}
     */
    this.x1 = -1;
    /**
     * The right most edge.
     * @public
     * @instance
     * @type {number}
     */
    this.x2 = -1;
    /**
     * The top most edge.
     * @public
     * @instance
     * @type {number}
     */
    this.y1 = -1;
    /**
     * The bottom most edge.
     * @public
     * @instance
     * @type {number}
     */
    this.y2 = -1;
    /**
     * The total width of the points.
     * @public
     * @instance
     * @type {number}
     */
    this.width = -1;
    /**
     * The total height of the points.
     * @public
     * @instance
     * @type {number}
     */
    this.height = -1;
}

/**
 * Constructor, creates a new instance of this type.
 * @classdesc This class represents an EasyScript protocol
 * @constructor
 */
function STNSignatureProtocol() {
    "use strict";
    /**
     * This string contains a textual name of this protocol.
     * @public
     * @instance
     * @type {string}
     */
    this.protocolName = "Standard EasyScript Protocol";
    /**
     * This character represents the glyph that starts a signature stream.
     * @public
     * @instance
     * @type {string}
     */
    this.startStream = '~';
    /**
     * This character represents the glyph that ends a signature stream.
     * @public
     * @instance
     * @type {string}
     */
    this.endStream = '`';
    /**
     * This character represents the glyph that ends a signature stream indicating it is cancelled.
     * @public
     * @instance
     * @type {string}
     */
    this.cancelStream = '/';
    /**
     * This character represents the glyph that signifies a break between strokes.
     * @public
     * @instance
     * @type {string}
     */
    this.penUp = ' ';
    /**
     * This string represents the "sentinel" that must be present before considering this the correct protocol.
     * @public
     * @instance
     * @type {string}
     */
    this.sentinel = "STSIGN";
    /**
     * The width in pixels of the devices using this protocol.
     * @public
     * @instance
     * @type {number}
     */
    this.width = 240;
    /**
     * The height in pixels of the devices using this protocol.
     * @public
     * @instance
     * @type {number}
     */
    this.height = 64;
    /**
     * This table contains the character codes and values of the most and least significant bytes of
     * the x and y coordinates. This is required to accurately decode a signature.
     * @public
     * @instance
     * @type {object<string,array<array<number>>>}
     */
    this.valueTable = {
        "x": [
            [0x41, 0x43, 0x45, 0x47, 0x49, 0x4b, 0x4d, 0x4f, 0x51, 0x53, 0x55, 0x57, 0x59, 0x21, 0x23, 0x25, 0x26, 0x28, 0x2b, 0x7d, 0x22, 0x3c],
            [0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x40, 0x24, 0x5e, 0x2a, 0x29, 0x5f, 0x7b, 0x7c, 0x3a, 0x3e]
        ],
        "y": [
            [0x61, 0x63, 0x65, 0x67, 0x69, 0x6b, 0x6d, 0x6f, 0x71, 0x73, 0x75, 0x77, 0x79, 0x31, 0x33, 0x35, 0x37, 0x39, 0x3d, 0x5d, 0x27, 0x2c],
            [0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x32, 0x34, 0x36, 0x38, 0x30, 0x2d, 0x5b, 0x5c, 0x3b, 0x2e]
        ]
    };
}


/**
 * Constructor, creates a new instance of this type.
 * @classdesc This class represents an EasyScript card swipe protocol
 * @constructor
 */
function STNCardSwipeProtocol() {
    "use strict";
    /**
     * This string contains a textual name of this protocol.
     * @public
     * @instance
     * @type {string}
     */
    this.protocolName = "Standard EasyScript Card Swipe Protocol";
    /**
     * This character represents the glyph that starts a signature stream.
     * @public
     * @instance
     * @type {string}
     */
    this.startStream = '!';
    /**
     * This character represents the glyph that ends a signature stream.
     * @public
     * @instance
     * @type {string}
     */
    this.endStream = '\r';
    /**
     * This string represents the "sentinel" that must be present before considering this the correct protocol.
     * @public
     * @instance
     * @type {string}
     */
    this.sentinel = "STCARD";

    /**
     * This boolean toggles whether the library should process the card data or not.  If passthrough is true
     * the card data keystrokes will have the Scriptel sentinel and version ID stripped off but allow the 
     * card data keystrokes to occur as normal.  Making the signature pad appear to be a standard mag stripe reader
     * to external software.
     * @public
     * @instance
     * @type {boolean}
     */
    this.passThrough = false;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the parsed first track of a financial card.
 * @constructor
 */
function FinancialTrackOneData() {
    "use strict";
    /**
     * Contains the account number of the financial card.
     * @public
     * @instance
     * @type {string}
     */
    this.accountNumber = "";
    /**
     * Contains the first name of the card holder.
     * @public
     * @instance
     * @type {string}
     */
    this.firstName = "";
    /**
     * Contains the last name of the card holder
     * @public
     * @instance
     * @type {string}
     */
    this.lastName = "";
    /**
     * Contains the expiration date of the card.
     * @public
     * @instance
     * @type {Date}
     */
    this.expiration = false;
    /**
     * Contains the service code of the card.
     * @public
     * @instance
     * @type {string}
     */
    this.serviceCode = "";
    /**
     * Contains any other data the card issuer wanted to include on the first track.
     * @public
     * @instance
     * @type {string}
     */
    this.discretionaryData = "";
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the parsed second track of a financial card.
 * @constructor
 */
function FinancialTrackTwoData() {
    "use strict";
    /**
     * Contains the account number of the financial card.
     * @public
     * @instance
     * @type {string}
     */
    this.accountNumber = "";
    /**
     * Contains the expiration date of the card.
     * @public
     * @instance
     * @type {Date}
     */
    this.expiration = false;
    /**
     * Contains the service code of the card.
     * @public
     * @instance
     * @type {string}
     */
    this.serviceCode = "";
    /**
     * Contains any other data the card issuer wanted to include on the first track.
     * @public
     * @instance
     * @type {string}
     */
    this.discretionaryData = "";
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the parsed first track of an identification card.
 * @constructor
 */
function IdentificationTrackOneData() {
    "use strict";
    /**
     * Two character state code.
     * @public
     * @instance
     * @type {string}
     */
    this.state = "";
    /**
     * City of residence.
     * @public
     * @instance
     * @type {string}
     */
    this.city = "";
    /**
     * Last name of the card holder.
     * @public
     * @instance
     * @type {string}
     */
    this.lastName = "";
    /**
     * First name of the card holder.
     * @public
     * @instance
     * @type {string}
     */
    this.firstName = "";
    /**
     * Middle name of the card holder.
     * @public
     * @instance
     * @type {string}
     */
    this.middleName = "";
    /**
     * Home address of the card holder.
     * @public
     * @instance
     * @type {string}
     */
    this.homeAddress = "";
    /**
     * Any additional information the issuer wanted to include.
     * @public
     * @instance
     * @type {string}
     */
    this.discretionaryData = "";
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the parsed second track of an identification card.
 * @constructor
 */
function IdentificationTrackTwoData() {
    "use strict";
    /**
     * Issuer identification number.
     * @public
     * @instance
     * @type {string}
     */
    this.issuerNumber = "";
    /**
     * Cardholder identification number (license number).
     * @public
     * @instance
     * @type {string}
     */
    this.idNumber = "";
    /**
     * Card expiration date.
     * @public
     * @instance
     * @type {date}
     */
    this.expiration = false;
    /**
     * Cardholder birth date.
     * @public
     * @instance
     * @type {date}
     */
    this.birthDate = false;
}

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class represents the parsed third track of an identification card.
 * @constructor
 */
function IdentificationTrackThreeData() {
    "use strict";
    /**
     * Specification version.
     * @public
     * @instance
     * @type {number}
     */
    this.cdsVersion = -1;
    /**
     * Jurisdiction version.
     * @public
     * @instance
     * @type {number}
     */
    this.jurisdictionVersion = -1;
    /**
     * Either full or truncated postcal code.
     * @public
     * @instance
     * @type {string}
     */
    this.zipCode = -1;
    /**
     * The class of this identification card/drivers license.
     * @public
     * @instance
     * @type {string}
     */
    this.licenseClass = "";
    /**
     * Restriction codes designated by issuer.
     * @public
     * @instance
     * @type {string}
     */
    this.restrictions = "";
    /**
     * Endorsement codes designated by issuer.
     * @public
     * @instance
     * @type {string}
     */
    this.endorsements = "";
    /**
     * Cardholder sex, M for male, F for female.
     * @public
     * @instance
     * @type {string}
     */
    this.sex = "";
    /**
     * Height of the cardholder.
     * @public
     * @instance
     * @type {string}
     */
    this.height = "";
    /**
     * Weight of the cardholder.
     * @public
     * @instance
     * @type {string}
     */
    this.weight = "";
    /**
     * Hair color of the cardholder.
     * @public
     * @instance
     * @type {string}
     */
    this.hairColor = "";
    /**
     * Eye color of the cardholder.
     * @public
     * @instance
     * @type {string}
     */
    this.eyeColor = "";
    /**
     * Any additional information included by the issuer.
     * @public
     * @instance
     * @type {string}
     */
    this.discretionaryData = "";
}

/**
 * Constructor for each node of a binary tree.
 * @constructor
 */
function BinaryTree() {
    "use strict";
    this.value = null;
    this.left = null;
    this.right = null;
}

/**
 * Constructor for a class for reading a binary tree from a string of '0's and '1's as described at
 * https://www.siggraph.org/education/materials/HyperGraph/video/mpeg/mpegfaq/huffman_tutorial.html
 * @constructor
 */
function BinaryTreeReader() {
    "use strict";
    this.location = 0;
    this.bitDepth = 1;
    this.table = null;
}

/**
 * Reads the next location location(s) in the binary string and returns the
 * bits as an integer.
 * @private
 * @method
 * @param bitdepth {int} the number of bits to read
 * @returns {int} the value of the bits read
 */
BinaryTreeReader.prototype.ReadTableValue = function (bitDepth) {
    "use strict";
    var c, i;
    var v = 0;
    for (i = 0; i < bitDepth; i++) {
        c = this.table.charAt(this.location++);
        v = (v * 2) + ((c === '0') ? 0 : 1);
    }
    return v;
};

/**
 * Writes the binary tree from the ascii bit table.
 * @private
 * @method
 * @return {BinaryTree} the head of the tree.
 */
BinaryTreeReader.prototype.WriteTree = function () {
    "use strict";
    var node = new BinaryTree();
    var type = this.ReadTableValue(1);
    if (type === 0) {
        node.left = this.WriteTree();
        node.right = this.WriteTree();
    } else {
        node.value = this.ReadTableValue(this.bitDepth);
    }
    return node;
};

/**
 * Sets up a high resolution 12 bit, or low resolution (9 bit) binary tree.
 * @private
 * @method
 * @return {BinaryTree} the head of the just created binary tree.
 */
BinaryTreeReader.prototype.SetupCompressionTree = function () {
    "use strict";
    this.table =
            "0011000000100000011101101000000001011100000000001010000010000000001000000000" +
            "1000100000001100010000000100010010000000111010010000001010110010001110000000" +
            "1011101000000100101000000000010010010000001001110000000010010000000001010100" +
            "0000001000101000000100000001111010000000000110001000000010100100000011110000" +
            "1000000101010100000001011110000000100110100000000011101000000001110100000000" +
            "1100000000100000011001110000001101110100000010001101000000111010100000011001" +
            "0001000000011011100000001110010000000010100100000011110101000011000000010000" +
            "0010011001000000100111001000110000000000100111100000001101001000000101111000" +
            "0000101000100000001010000000000100101100000010000001011010010000000000000010" +
            "0000010010101000000101110100000011000110000000100000010000000011011000000010" +
            "1100100000000010110000000100100000100000000011001000000010101100000010100001" +
            "0000000010001000000001111000001000000110110000010011000000001001101000000001" +
            "0101010000001011001000000011001100000001100010000000001010010000000101101000" +
            "0000010101110000001011011000000000010111110000001101000000000011000000000001" +
            "1011000000000011000010000001101101000000011001010000001010110000000000110001" +
            "1000000101100000000001101010000000110011100000000110010000000010100110000000" +
            "1101011000000101110000000010001000000000100000010001011011100000001000000000" +
            "0011000000111110000100000000100101000000011111010000001010011000000101111100" +
            "0010000000000010000001110010100000011010010000001110001000000101100010000001" +
            "1111110000001000010001000000110101100000010000010000000110100010000001100001" +
            "0000001001001000000011001";
    this.location = 0;
    this.bitDepth = this.ReadTableValue(6);
    var head = this.WriteTree();
    this.table = null;
    return head;
};


/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class is responsible for providing transitional mapping from 
 * keyCode to code in lieu of actual browser support. This should not be used
 * from external code and will probably be removed from the library at some
 * point when browser support for code gets more ubiquitous.
 * @constructor
 */
function ScriptelInternationalization(layout) {
    "use strict";

    this.variants = [
        {
            "name": "None",
            "remap": [],
            "id": [192, 187, 189, 219, 89],
            "layouts": [
                "Arabic (101)",
                "Arabic (102)",
                "Arabic (102) AZERTY",
                "Armenian Phonetic",
                "Assamese (India) Assamese - INSCRIPT",
                "Azeri Cyrillic",
                "Bangla (India) - Bengali - INSCRIPT",
                "Bangla (India) - Bengali - INSCRIPT (Legacy)",
                "Bashkir",
                "Belarusian",
                "Bengali",
                "Bosnian (Cyrillic)",
                "Bulgarian (Latin)",
                "Bulgarian (Phonetic Traditional)",
                "Central Kurdish",
                "Chinese Microsoft Bopomofo",
                "Chinese Microsoft ChangJie",
                "Chinese Microsoft Quick",
                "Czech (QWERTY)",
                "Czech Programmers",
                "Divehi Phonetic",
                "Divehi Typewriter",
                "English (India)",
                "Georgian (Ergonomic)",
                "Georgian (Legacy)",
                "Georgian (MES)",
                "Georgian (Old Alphabets)",
                "Georgian (QWERTY)",
                "Greek",
                "Greek Latin",
                "Greek Polytonic",
                "Gujarati",
                "Hausa",
                "Hawaiian",
                "Hebrew",
                "Hebrew (Standard)",
                "Hindi (India) - Devanagari - INSCRIPT",
                "Hindi Traditional",
                "Igbo",
                "Japanese",
                "Kannada",
                "Kazakh",
                "Korean",
                "Kyrgyz Cyrillic",
                "Lao",
                "Latvian (QWERTY)",
                "Latvian (Standard)",
                "Lithuanian",
                "Lithuanian IBM",
                "Macedonian (FYROM)",
                "Malayalam",
                "Maltese 47-Key",
                "Maltese 48-Key",
                "Maori",
                "Marathi",
                "Mongolian (Mongolian Script)",
                "Mongolian Cyrillic",
                "Nepali",
                "Odia",
                "Pashto (Afghanistan)",
                "Persian",
                "Persian (Standard)",
                "Polish (Programmers)",
                "Portuguese (Brazil ABNT)",
                "Portuguese (Brazil ABNT2)",
                "Punjabi",
                "Romanian (Programmers)",
                "Romanian (Standard)",
                "Russian",
                "Russian (Typewriter)",
                "Russian - Mnemonic",
                "Sakha",
                "Serbian (Cyrillic)",
                "Sesotho sa Leboa",
                "Setswana",
                "Sinhala",
                "Sinhala - Wij 9",
                "Slovak (QWERTY)",
                "Syriac",
                "Syriac Phonetic",
                "Tajik",
                "Tamil",
                "Tatar",
                "Tatar (Legacy)",
                "Telugu",
                "Thai Kedmanee",
                "Thai Kedmanee (non-ShiftLock)",
                "Thai Pattachote",
                "Thai Pattachote (non-ShiftLock)",
                "Tibetan (PRC)",
                "Tibetan (PRC) - Updated",
                "Traditional Mongolian (Standard)",
                "Turkmen",
                "Ukrainian",
                "Ukrainian (Enhanced)",
                "United States-International",
                "Urdu",
                "US",
                "English (US)",
                "US English Table for IBM Arabic 238_L",
                "Uyghur",
                "Uyghur (Legacy)",
                "Uzbek Cyrillic",
                "Vietnamese",
                "Yoruba"
            ]
        },
        {
            "name": "Variation 2",
            "remap": [
                {"actual": 222, "expected": 192},
                {"actual": 192, "expected": 219},
                {"actual": 219, "expected": 221},
                {"actual": 191, "expected": 222},
                {"actual": 221, "expected": 191}
            ],
            "id": [222, 187, 189, 192, 89],
            "layouts": [
                "Estonian"
            ]
        },
        {
            "name": "Variation 3",
            "remap": [
                {"actual": 48, "expected": 192},
                {"actual": 192, "expected": 48},
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 191, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [48, 187, 191, 219, 90],
            "layouts": [
                "Hungarian"
            ]
        },
        {
            "name": "Variation 4",
            "remap": [
                {"actual": 48, "expected": 192},
                {"actual": 192, "expected": 48}
            ],
            "id": [48, 187, 189, 219, 89],
            "layouts": [
                "Hungarian 101-key"
            ]
        },
        {
            "name": "Variation 5",
            "remap": [
                {"actual": 89, "expected": 219},
                {"actual": 72, "expected": 221},
                {"actual": 70, "expected": 187},
                {"actual": 186, "expected": 65},
                {"actual": 80, "expected": 66},
                {"actual": 221, "expected": 67},
                {"actual": 83, "expected": 68},
                {"actual": 74, "expected": 69},
                {"actual": 73, "expected": 70},
                {"actual": 76, "expected": 71},
                {"actual": 68, "expected": 72},
                {"actual": 90, "expected": 73},
                {"actual": 65, "expected": 74},
                {"actual": 84, "expected": 75},
                {"actual": 69, "expected": 76},
                {"actual": 223, "expected": 77},
                {"actual": 79, "expected": 78},
                {"actual": 87, "expected": 79},
                {"actual": 88, "expected": 80},
                {"actual": 85, "expected": 83},
                {"actual": 77, "expected": 84},
                {"actual": 78, "expected": 85},
                {"actual": 75, "expected": 86},
                {"actual": 71, "expected": 87},
                {"actual": 66, "expected": 88},
                {"actual": 86, "expected": 89},
                {"actual": 219, "expected": 90},
                {"actual": 67, "expected": 186}
            ],
            "id": [192, 70, 189, 89, 86],
            "layouts": [
                "Latvian"
            ]
        },
        {
            "name": "Variation 6",
            "remap": [
                {"actual": 220, "expected": 192},
                {"actual": 191, "expected": 220},
                {"actual": 187, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 221, "expected": 187},
                {"actual": 219, "expected": 189},
                {"actual": 192, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [220, 221, 219, 187, 89],
            "layouts": [
                "Portuguese"
            ]
        },
        {
            "name": "Variation 7",
            "remap": [
                {"actual": 223, "expected": 187},
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 191, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [192, 223, 191, 219, 90],
            "layouts": [
                "Slovak"
            ]
        },
        {
            "name": "Variation 8",
            "remap": [
                {"actual": 220, "expected": 192},
                {"actual": 191, "expected": 220},
                {"actual": 221, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 219, "expected": 187},
                {"actual": 192, "expected": 186},
                {"actual": 223, "expected": 191}
            ],
            "id": [220, 219, 189, 221, 89],
            "layouts": [
                "Spanish Variation"
            ]
        },
        {
            "name": "Variation 9",
            "remap": [
                {"actual": 88, "expected": 220},
                {"actual": 81, "expected": 219},
                {"actual": 87, "expected": 221},
                {"actual": 66, "expected": 188},
                {"actual": 189, "expected": 187},
                {"actual": 85, "expected": 65},
                {"actual": 191, "expected": 66},
                {"actual": 86, "expected": 67},
                {"actual": 69, "expected": 68},
                {"actual": 186, "expected": 69},
                {"actual": 65, "expected": 70},
                {"actual": 221, "expected": 71},
                {"actual": 84, "expected": 72},
                {"actual": 78, "expected": 73},
                {"actual": 75, "expected": 74},
                {"actual": 77, "expected": 75},
                {"actual": 83, "expected": 77},
                {"actual": 90, "expected": 78},
                {"actual": 72, "expected": 79},
                {"actual": 70, "expected": 81},
                {"actual": 73, "expected": 82},
                {"actual": 219, "expected": 83},
                {"actual": 79, "expected": 84},
                {"actual": 82, "expected": 85},
                {"actual": 67, "expected": 86},
                {"actual": 71, "expected": 87},
                {"actual": 220, "expected": 88},
                {"actual": 68, "expected": 89},
                {"actual": 74, "expected": 90},
                {"actual": 187, "expected": 189},
                {"actual": 89, "expected": 186},
                {"actual": 188, "expected": 191}
            ],
            "id": [192, 189, 187, 81, 68],
            "layouts": [
                "Turkish F"
            ]
        },
        {
            "name": "Variation 10",
            "remap": [
                {"actual": 188, "expected": 220},
                {"actual": 191, "expected": 188},
                {"actual": 189, "expected": 187},
                {"actual": 223, "expected": 189},
                {"actual": 220, "expected": 190},
                {"actual": 190, "expected": 191}
            ],
            "id": [192, 189, 223, 219, 89],
            "layouts": [
                "Turkish Q"
            ]
        },
        {
            "name": "Variation 11",
            "remap": [
                {"actual": 191, "expected": 219},
                {"actual": 187, "expected": 221},
                {"actual": 87, "expected": 188},
                {"actual": 221, "expected": 187},
                {"actual": 88, "expected": 66},
                {"actual": 74, "expected": 67},
                {"actual": 69, "expected": 68},
                {"actual": 190, "expected": 69},
                {"actual": 85, "expected": 70},
                {"actual": 73, "expected": 71},
                {"actual": 68, "expected": 72},
                {"actual": 67, "expected": 73},
                {"actual": 72, "expected": 74},
                {"actual": 84, "expected": 75},
                {"actual": 78, "expected": 76},
                {"actual": 66, "expected": 78},
                {"actual": 82, "expected": 79},
                {"actual": 76, "expected": 80},
                {"actual": 222, "expected": 81},
                {"actual": 80, "expected": 82},
                {"actual": 79, "expected": 83},
                {"actual": 89, "expected": 84},
                {"actual": 71, "expected": 85},
                {"actual": 75, "expected": 86},
                {"actual": 188, "expected": 87},
                {"actual": 81, "expected": 88},
                {"actual": 70, "expected": 89},
                {"actual": 186, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 86, "expected": 190},
                {"actual": 189, "expected": 222},
                {"actual": 83, "expected": 186},
                {"actual": 90, "expected": 191}
            ],
            "id": [192, 221, 219, 191, 70],
            "layouts": [
                "United States - Dvorak"
            ]
        },
        {
            "name": "Variation 12",
            "remap": [
                {"actual": 53, "expected": 219},
                {"actual": 187, "expected": 221},
                {"actual": 51, "expected": 48},
                {"actual": 219, "expected": 49},
                {"actual": 221, "expected": 50},
                {"actual": 191, "expected": 51},
                {"actual": 80, "expected": 52},
                {"actual": 70, "expected": 53},
                {"actual": 77, "expected": 54},
                {"actual": 76, "expected": 55},
                {"actual": 74, "expected": 56},
                {"actual": 52, "expected": 57},
                {"actual": 49, "expected": 187},
                {"actual": 189, "expected": 65},
                {"actual": 87, "expected": 66},
                {"actual": 71, "expected": 67},
                {"actual": 67, "expected": 68},
                {"actual": 66, "expected": 69},
                {"actual": 68, "expected": 70},
                {"actual": 84, "expected": 71},
                {"actual": 79, "expected": 73},
                {"actual": 69, "expected": 74},
                {"actual": 65, "expected": 75},
                {"actual": 90, "expected": 76},
                {"actual": 73, "expected": 77},
                {"actual": 190, "expected": 79},
                {"actual": 54, "expected": 80},
                {"actual": 186, "expected": 81},
                {"actual": 89, "expected": 82},
                {"actual": 75, "expected": 83},
                {"actual": 85, "expected": 84},
                {"actual": 83, "expected": 85},
                {"actual": 81, "expected": 87},
                {"actual": 82, "expected": 89},
                {"actual": 222, "expected": 90},
                {"actual": 50, "expected": 189},
                {"actual": 48, "expected": 190},
                {"actual": 55, "expected": 222},
                {"actual": 56, "expected": 186},
                {"actual": 57, "expected": 191}
            ],
            "id": [192, 49, 50, 53, 82],
            "layouts": [
                "United States-Dvorak for left hand"
            ]
        },
        {
            "name": "Variation 13",
            "remap": [
                {"actual": 186, "expected": 219},
                {"actual": 187, "expected": 221},
                {"actual": 86, "expected": 188},
                {"actual": 191, "expected": 48},
                {"actual": 74, "expected": 53},
                {"actual": 76, "expected": 54},
                {"actual": 77, "expected": 55},
                {"actual": 70, "expected": 56},
                {"actual": 80, "expected": 57},
                {"actual": 221, "expected": 187},
                {"actual": 55, "expected": 65},
                {"actual": 73, "expected": 66},
                {"actual": 88, "expected": 67},
                {"actual": 90, "expected": 68},
                {"actual": 81, "expected": 69},
                {"actual": 65, "expected": 70},
                {"actual": 69, "expected": 71},
                {"actual": 85, "expected": 73},
                {"actual": 84, "expected": 74},
                {"actual": 68, "expected": 75},
                {"actual": 67, "expected": 76},
                {"actual": 87, "expected": 77},
                {"actual": 89, "expected": 79},
                {"actual": 66, "expected": 80},
                {"actual": 53, "expected": 81},
                {"actual": 190, "expected": 82},
                {"actual": 56, "expected": 83},
                {"actual": 79, "expected": 84},
                {"actual": 83, "expected": 85},
                {"actual": 188, "expected": 86},
                {"actual": 54, "expected": 87},
                {"actual": 48, "expected": 88},
                {"actual": 82, "expected": 89},
                {"actual": 57, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 71, "expected": 190},
                {"actual": 189, "expected": 222},
                {"actual": 75, "expected": 186},
                {"actual": 222, "expected": 191}
            ],
            "id": [192, 221, 219, 186, 82],
            "layouts": [
                "United States-Dvorak for right hand"
            ]
        },
        {
            "name": "Variation 14",
            "remap": [
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90}
            ],
            "id": [192, 187, 189, 219, 90],
            "layouts": [
                "Albanian",
                "Romanian (Legacy)"
            ]
        },
        {
            "name": "Variation 15",
            "remap": [
                {"actual": 223, "expected": 188},
                {"actual": 190, "expected": 187},
                {"actual": 188, "expected": 81},
                {"actual": 81, "expected": 190}
            ],
            "id": [192, 190, 189, 219, 89],
            "layouts": [
                "Bulgarian",
                "Bulgarian (Typewriter)"
            ]
        },
        {
            "name": "Variation 16",
            "remap": [
                {"actual": 191, "expected": 187},
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 187, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [192, 191, 187, 219, 90],
            "layouts": [
                "Czech",
                "Polish (214)"
            ]
        },
        {
            "name": "Variation 17",
            "remap": [
                {"actual": 187, "expected": 219},
                {"actual": 219, "expected": 187},
                {"actual": 191, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [192, 219, 191, 187, 89],
            "layouts": [
                "Greek (220) Latin",
                "Greek (220)"
            ]
        },
        {
            "name": "Variation 18",
            "remap": [
                {"actual": 191, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [192, 187, 191, 219, 89],
            "layouts": [
                "Greek (319) Latin",
                "Greek (319)"
            ]
        },
        {
            "name": "Variation 19",
            "remap": [
                {"actual": 222, "expected": 192},
                {"actual": 221, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 190, "expected": 188},
                {"actual": 189, "expected": 187},
                {"actual": 81, "expected": 65},
                {"actual": 188, "expected": 77},
                {"actual": 65, "expected": 81},
                {"actual": 90, "expected": 87},
                {"actual": 87, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 191, "expected": 190},
                {"actual": 192, "expected": 222},
                {"actual": 77, "expected": 186},
                {"actual": 187, "expected": 191}
            ],
            "id": [222, 189, 219, 221, 89],
            "layouts": [
                "Belgian (Comma)",
                "Belgian (Period)",
                "Belgian French"
            ]
        },
        {
            "name": "Variation 20",
            "remap": [
                {"actual": 222, "expected": 192},
                {"actual": 192, "expected": 222}
            ],
            "id": [222, 187, 189, 219, 89],
            "layouts": [
                "Candian Multilingual Standard",
                "Multilingual (Canada)",
                "French (Canada)",
                "Canadian French",
                "Canadian French (Legacy)"
            ]
        },
        {
            "name": "Variation 21",
            "remap": [
                {"actual": 222, "expected": 192},
                {"actual": 221, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 190, "expected": 188},
                {"actual": 81, "expected": 65},
                {"actual": 188, "expected": 77},
                {"actual": 65, "expected": 81},
                {"actual": 90, "expected": 87},
                {"actual": 87, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 191, "expected": 190},
                {"actual": 192, "expected": 222},
                {"actual": 77, "expected": 186},
                {"actual": 223, "expected": 191}
            ],
            "id": [222, 187, 219, 221, 89],
            "layouts": [
                "Central Atlas Tamazight",
                "French",
                "Wolof"
            ]
        },
        {
            "name": "Variation 22",
            "remap": [
                {"actual": 191, "expected": 192},
                {"actual": 223, "expected": 220},
                {"actual": 186, "expected": 219},
                {"actual": 192, "expected": 221},
                {"actual": 221, "expected": 187},
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 220, "expected": 222},
                {"actual": 222, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [191, 221, 219, 186, 90],
            "layouts": [
                "Luxembourgish",
                "Swiss French",
                "Swiss German"
            ]
        },
        {
            "name": "Variation 23",
            "remap": [
                {"actual": 222, "expected": 192},
                {"actual": 221, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 191, "expected": 187},
                {"actual": 219, "expected": 189},
                {"actual": 192, "expected": 222},
                {"actual": 187, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [222, 191, 219, 221, 89],
            "layouts": [
                "Dutch",
                "Armenian Eastern (Legacy)",
                "Armenian Typewriter",
                "Armenian Western (Legacy)"
            ]
        },
        {
            "name": "Variation 24",
            "remap": [
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 191, "expected": 189},
                {"actual": 189, "expected": 191}
            ],
            "id": [192, 187, 191, 219, 90],
            "layouts": [
                "Croatian",
                "Serbian (Latin)",
                "Slovenian",
                "Bulgarian (Phonetic)",
                "Macedonian (FYROM) - Standard"
            ]
        },
        {
            "name": "Variation 25",
            "remap": [
                {"actual": 220, "expected": 192},
                {"actual": 191, "expected": 220},
                {"actual": 186, "expected": 219},
                {"actual": 187, "expected": 221},
                {"actual": 221, "expected": 187},
                {"actual": 90, "expected": 89},
                {"actual": 89, "expected": 90},
                {"actual": 219, "expected": 189},
                {"actual": 192, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [220, 221, 219, 186, 90],
            "layouts": [
                "German",
                "German (IBM)",
                "Sorbian Extended",
                "Sorbian Standard",
                "Sorbian Standard (Legacy)"
            ]
        },
        {
            "name": "Variation 26",
            "remap": [
                {"actual": 220, "expected": 192},
                {"actual": 191, "expected": 220},
                {"actual": 186, "expected": 219},
                {"actual": 187, "expected": 221},
                {"actual": 221, "expected": 187},
                {"actual": 219, "expected": 189},
                {"actual": 192, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [220, 221, 219, 186, 89],
            "layouts": [
                "Guarani",
                "Italian",
                "Italian (142)",
                "Latin American",
                "Spanish (Latin American)",
                "Spanish"
            ]
        },
        {
            "name": "Variation 27",
            "remap": [
                {"actual": 223, "expected": 192},
                {"actual": 222, "expected": 220},
                {"actual": 192, "expected": 222}
            ],
            "id": [223, 187, 189, 219, 89],
            "layouts": [
                "Irish",
                "Scottish Gaelic",
                "United Kingdom",
                "English (UK)",
                "Azeri Latin",
                "Inuktitut - Latin",
                "United Kingdom Extended",
                "Inuktitut - Naqittuat"
            ]
        },
        {
            "name": "Variation 28",
            "remap": [
                {"actual": 220, "expected": 192},
                {"actual": 191, "expected": 220},
                {"actual": 221, "expected": 219},
                {"actual": 186, "expected": 221},
                {"actual": 219, "expected": 187},
                {"actual": 187, "expected": 189},
                {"actual": 192, "expected": 186},
                {"actual": 189, "expected": 191}
            ],
            "id": [220, 219, 187, 221, 89],
            "layouts": [
                "Danish",
                "Faeroese",
                "Finnish",
                "Finnish with Sami",
                "Greenlandic",
                "Icelandic",
                "Lithuanian Standard",
                "Norwegian",
                "Norwegian with Sami",
                "Sami Extended Finland-Sweden",
                "Sami Extended Norway",
                "Swedish",
                "Swedish with Sami"
            ]
        }

    ];

    /**
     * Keyboard map to take codes and translate them to English US keybaords.
     */
    this.codeMap = {
        //First Row
        "Backquote": ['`', '~'],
        "Digit1": ['1', '!'],
        "Digit2": ['2', '@'],
        "Digit3": ['3', '#'],
        "Digit4": ['4', '$'],
        "Digit5": ['5', '%'],
        "Digit6": ['6', '^'],
        "Digit7": ['7', '&'],
        "Digit8": ['8', '*'],
        "Digit9": ['9', '('],
        "Digit0": ['0', ')'],
        "Minus": ['-', '_'],
        "Equal": ['=', '+'],
        //Second Row
        "KeyQ": ['q', 'Q'],
        "KeyW": ['w', 'W'],
        "KeyE": ['e', 'E'],
        "KeyR": ['r', 'R'],
        "KeyT": ['t', 'T'],
        "KeyY": ['y', 'Y'],
        "KeyU": ['u', 'U'],
        "KeyI": ['i', 'I'],
        "KeyO": ['o', 'O'],
        "KeyP": ['p', 'P'],
        "BracketLeft": ['[', '{'],
        "BracketRight": [']', '}'],
        "Backslash": ['\\', '|'],
        //Third Row
        "KeyA": ['a',  'A'],
        "KeyS": ['s', 'S'],
        "KeyD": ['d', 'D'],
        "KeyF": ['f', 'F'],
        "KeyG": ['g', 'G'],
        "KeyH": ['h', 'H'],
        "KeyJ": ['j', 'J'],
        "KeyK": ['k', 'K'],
        "KeyL": ['l', 'L'],
        "Semicolon": [';', ':'],
        "Quote": ['\'', '"'],
        "Enter": ['\r', '\r'],
        //Forth Row
        "KeyZ": ['z', 'Z'],
        "KeyX": ['x', 'X'],
        "KeyC": ['c', 'C'],
        "KeyV": ['v', 'V'],
        "KeyB": ['b', 'B'],
        "KeyN": ['n', 'N'],
        "KeyM": ['m', 'M'],
        "Comma": [',', '<'],
        "Period": ['.', '>'],
        "Slash": ['/', '?'],
        //Space
        "Space": [' ', ' '],
        //Special case, this key comes up sometimes but it doesn't have an English mapping, these are just dummy mappings.
        "Unknown": ['`', '~']
    };
    /*
     * Keyboard map to take keyCodes and translate to code.
     */
    this.keyCodeMap = {
        192: "Backquote",
        49:  "Digit1",
        50:  "Digit2",
        51:  "Digit3",
        52:  "Digit4",
        53:  "Digit5",
        54:  "Digit6",
        55:  "Digit7",
        56:  "Digit8",
        57:  "Digit9",
        48:  "Digit0",
        189: "Minus",
        187: "Equal",
        81:  "KeyQ",
        87:  "KeyW",
        69:  "KeyE",
        82:  "KeyR",
        84:  "KeyT",
        89:  "KeyY",
        85:  "KeyU",
        73:  "KeyI",
        79:  "KeyO",
        80:  "KeyP",
        219: "BracketLeft",
        221: "BracketRight",
        220: "Backslash",
        65:  "KeyA",
        83:  "KeyS",
        68:  "KeyD",
        70:  "KeyF",
        71:  "KeyG",
        72:  "KeyH",
        74:  "KeyJ",
        75:  "KeyK",
        76:  "KeyL",
        186: "Semicolon",
        222: "Quote",
        13:  "Enter",
        90:  "KeyZ",
        88:  "KeyX",
        67:  "KeyC",
        86:  "KeyV",
        66:  "KeyB",
        78:  "KeyN",
        77:  "KeyM",
        188: "Comma",
        190: "Period",
        191: "Slash",
        32:  "Space",

        //These are here because some browsers have issues.
        223: "Unknown",
        //OSX Firefox
        173: "Minus",
        61: "Equal",
        59: "Semicolon"
    };

    this.variant = false;
    this.variantTable = false;

    if (layout) {
        //There are a couple special legacy cases to handle:
        if (layout === "us-basic") {
            layout = "English (US)";
        } else if (layout === "ca-multi") {
            layout = "Multilingual (Canada)";
        } else if (layout === "ca-fr") {
            layout = "French (Canada)";
        } else if (layout === "latam-basic") {
            layout = "Spanish (Latin American)";
        } else if (layout === "es-basic") {
            layout = "Spanish";
        } else if (layout === "gb-basic") {
            layout = "English (UK)";
        } else if (layout === "pt-basic") {
            layout = "Portuguese";
        } else if (layout === "dk-basic") {
            layout = "Danish";
        } else if (layout === "no-basic") {
            layout = "Norwegian";
        }

        this.variant = this.getLayout(layout);
        this.buildTable();
    }
}
/**
 * This method is responsible for getting a variant number from a layout
 * name.
 * @param layout {string} Layout to look up.
 * @return {number} Number of the variant the layout maps to, or false if it couldn't be found.
 */
ScriptelInternationalization.prototype.getLayout = function (layout) {
    "use strict";
    var i, j;
    for (i = 0; i < this.variants.length; i++) {
        for (j = 0; j < this.variants[i].layouts.length; j++) {
            if (layout === this.variants[i].layouts[j]) {
                return i;
            }
        }
    }

    return false;
};
/**
 * This method is used to translate a particular key event into a character
 * for the currently configured variant table. If "code" is available no
 * variant table is used and it just uses code.
 * @param evt {DOMEvent} DOM event to inspect.
 * @return {string} A single character representing the mapped keyboard event, false if no mapping was found.
 */
ScriptelInternationalization.prototype.translateKey = function (evt) {
    "use strict";
    if (evt.code) {
        //Best case, we're golden here.
        if (this.codeMap[evt.code]) {
            return this.codeMap[evt.code][evt.shiftKey ? 1 : 0];
        }
        return false;
    }

    if (evt.which || evt.keyCode) {
        //Less good case, we're going to have to do some work.
        var keyCode = evt.which || evt.keyCode;
        var table = this.variantTable || this.keyCodeMap;
        var code = table[keyCode];
        if (code) {
            var chr = this.codeMap[code];
            if (chr) {
                return chr[evt.shiftKey ? 1 : 0];
            }
        }
        return false;
    }
};
/**
 * This method is responsible for building the variant table to use.
 * @param sig {array<number>} A signature beacon to use to identify a new variant table. Optional.
 */
ScriptelInternationalization.prototype.buildTable = function (sig) {
    "use strict";
    if (sig) {
        this.variant = this.identifyBeacon(sig);
    }

    //Create a copy of the map, TODO: this more elegantly.
    this.variantTable = JSON.parse(JSON.stringify(this.keyCodeMap));

    if (this.variant !== false && this.variant >= 0) {
        //We've got a real table.
        var tbl = this.variants[this.variant];
        var i;
        var remap;
        for (i = 0; i < tbl.remap.length; i++) {
            remap = tbl.remap[i];
            this.variantTable[remap.actual] = this.keyCodeMap[remap.expected];
        }
    }
};
/**
 * This function will attempt to determine which variant is currently
 * required to process further data based on examining a pre-defined
 * set of becaon characters.
 * @param sig {array<number>} Array of five keyCodes.
 * @return {number} Index of required variant, -1 if no variant is required or false if we couldn't tell.
 */
ScriptelInternationalization.prototype.identifyBeacon = function (sig) {
    "use strict";
    if (sig.length < 5) {
        return false;
    }

    var id = [sig[1], sig[2], sig[0], sig[3], sig[4]];
    var i, j, match;
    for (i = 0; i < this.variants.length; i++) {
        match = true;
        for (j = 0; match && j < id.length; j++) {
            if (id[j] !== this.variants[i].id[j]) {
                match = false;
            }
        }

        if (match) {
            return i;
        }
    }

    return false;
};

/**
 * This method attempts to get all of the different keyCode possiblities for a
 * particular charater. This is used to tenatively identify characters prior to
 * a signature beacon.
 * @param chr {string} Character to identify possibilities for.
 * @return {array<string>} Characters this character could potentially be in other mappings.
 */
ScriptelInternationalization.prototype.getPossibilities = function (chr) {
    "use strict";
    var code = this.getCharCode(chr);
    var keyCode = this.getKeyCode(code);
    var i, j;

    if (code === false || keyCode === false) {
        return false;
    }

    var shifted = (this.codeMap[code][1] === chr);

    var possibleKeyCodes = [ keyCode ];
    var variant;
    //Now lets crawl the variant table looking for variations.
    for (i = 0; i < this.variants.length; i++) {
        variant = this.variants[i];
        for (j = 0; j < variant.remap.length; j++) {
            if (variant.remap[j].expected === keyCode) {
                possibleKeyCodes[possibleKeyCodes.length] = variant.remap[j].actual;
            }
        }
    }

    var endChars = [];
    var keyTmp, codeTmp, chrCode;
    for (i = 0; i < possibleKeyCodes.length; i++) {
        keyTmp = possibleKeyCodes[i];
        codeTmp = this.keyCodeMap[keyTmp];
        chrCode = this.codeMap[codeTmp][shifted ? 1 : 0];

        if (endChars.indexOf(chrCode) < 0) {
            endChars[endChars.length] = chrCode;
        }
    }

    return endChars;
};

/**
 * This method attempts to map a keyCode to a pariticular character in the
 * current variant table.
 * @param keyCode {number} Key code to look up.
 * @param shifted {boolean} Whether or not the shift key was pressed.
 * @return {string} String representing the mapped character, false if the keyCode wasn't found, undefined if the mapping wasn't found.
 */
ScriptelInternationalization.prototype.mapCharacter = function (keyCode, shifted) {
    "use strict";
    if (!this.variantTable) {
        this.buildTable();
    }
    var code = this.variantTable[keyCode];
    if (!code) {
        return false;
    }
    return this.codeMap[code][shifted ? 1 : 0];
};

/**
 * This method attempts to get the code for a particular character using the US mapping.
 * @param chr {string} Character to look up.
 * @return {string} Code the character maps to.
 */
ScriptelInternationalization.prototype.getCharCode = function (chr) {
    "use strict";
    var code, i;

    for (code in this.codeMap) {
        if (this.codeMap.hasOwnProperty(code)) {
            for (i = 0; i < 2; i++) {
                if (this.codeMap[code][i] === chr) {
                    return code;
                }
            }
        }
    }
    return false;
};

/**
 * This method attempts to look up a key code for a particular code in the US mapping.
 * @param code {string} Code to look up.
 * @return {number} Code if found, false otherwise.
 */
ScriptelInternationalization.prototype.getKeyCode = function (code) {
    "use strict";
    var keyCode;
    for (keyCode in this.keyCodeMap) {
        if (this.keyCodeMap[keyCode] === code) {
            return parseInt(keyCode, 10);
        }
    }
    return false;
};

/**
 * Constructor, creates a new instance of this class.
 * @classdesc This class can be used to add event handlers to your web
 * pages that are capable of intercepting, parsing and rendering signature
 * streams from the Scriptel ScripTouch EasyScript series of devices.
 * @constructor
 */
function ScriptelEasyScript() {
    "use strict";
    /**
     * The list of all available signature parsing algorithms.
     * @public
     * @instance
     * @type {array<SignatureProtocol>}
     */
    this.signatureProtocols = [
        new STNSignatureProtocol()
    ];
    /**
     * The list of all available card parsing algorithms.
     * @public
     * @instance
     * @type {array<SignatureProtocol>}
     */
    this.cardSwipeProtocols = [
        new STNCardSwipeProtocol()
    ];
    /**
     * This property stores the current version of this library.
     * @public
     * @instance
     * @type {string}
     */
    this.libraryVersion = "3.5.29";
    /**
     * This property stores the date on which this library was built.
     * @public
     * @instance
     * @type {string}
     */
    this.libraryBuildDate = "2022-08-30 17:12:23-0400";
    /**
     * The protocol to be used to decode the signature string.
     * @public
     * @instance
     * @type {SignatureProtocol}
     */
    this.signatureProtocol = new STNSignatureProtocol();
    /**
     * The protocol to be used to decode the magnetic strip string.
     * @public
     * @instance
     * @type {CardSwipeProtocol}
     */
    this.cardSwipeProtocol = new STNCardSwipeProtocol();

    /**
     * This variable should be set to the keyboard layout you expect input to
     * be coming in from. Defaults to "US".
     * @public
     * @instance
     * @type {string}
     */
    this.fromKeyboardSpec = "US";

    /**
     * Deprecated
     * @public
     * @instance
     * @type {string}
     */
    this.toKeyboardSpec = undefined;

    /**
     * This object is used to support international keyboards.
     * @public
     * @instance
     * @type {InternationalKeyboard}
     */
    this.internationalKeyboard = undefined;

    /**
     * This callback function is called after capturing, but prior to processing
     * any keyboard data. This can be used to "reconfigure" the keyboard API prior
     * to processing to switch keyboard specifications, protocols, etc.
     * @public
     * @instance
     * @type {function}
     */
    this.configurationCallback = false;

    /**
     * A set of signatureCallbacks to call upon receiving a signature from the attached elements.
     * @private
     * @instance
     * @type {function}
     */
    this.signatureCallbacks = [];
    /**
     * A set of streamingCallbacks to call upon receiving information about a signature from the attached elements.
     * @private
     * @instance
     * @type {function}
     */
    this.streamingCallbacks = [];
    /**
     * Whether or not we're currently intercepting (blocking) key presses in anticipation of a signature.
     * @private
     * @instance
     * @type {boolean}
     */
    this.intercepting = false;
	/**
	 * Whether the last key was the terminator for a signature or card
	 * @private
	 * @instance
	 * @type (boolean)
	 */
	this.terminatingKey = false;
    /**
     * The count of characters coming in.
     * @private
     * @instance
     * @type {int}
     */
    this.charCount = 0;
    /**
     * Whether the timeout should be used. It is only used for legacy reasons.
     * @private
     * @instance
     * @type {boolean}
     */
    this.useTimeout = false;
    /**
     * The maximum time we're willing to wait between characters before we decide what we're capturing isn't a signature.
     * @private
     * @instance
     * @type {number}
     */
    this.timeout = 250;
    /**
     * The time stamp of the last key press.
     * @private
     * @instance
     * @type {number}
     */
    this.lastKey = -1;
    /**
     * This keeps track of the protocol currently being used to decode the capture buffer.
     * @private
     * @instance
     * @type {SignatureProtocol|CardSwipeProtocol}
     */
    this.lastProtocol = false;

    this.lastKeySpace = false;

    /**
     * The streaming state machine's state
     * @private
     * @instance
     */
    this.parserState = undefined;
    /**
     * A class variable to build strings in the parser.
     * @private
     * @instance
     */
    this.parserStringBuilder = undefined;
    /**
     * The card parsing state machine's state
     * @private
     * @instance
     */
    this.cardHandlerState = undefined;
    /**
     * A class variable to build strings for parsing cards.
     * @private
     * @instance
     */
    this.cardStringBuilder = undefined;
    /**
     * The position of the string being parsed
     * @private
     * @instance
     */
    this.parserStringPosition = undefined;
    /**
     * A place to store the meta data as it is being collected in streaming mode
     * @private
     * @instance
     */
    this.parserMetaData = null;
    this.legacyCallback = null;
    this.legacySignature = null;

    var reader = new BinaryTreeReader();
    this.highResDecompressionTree = reader.SetupCompressionTree();
    this.currentNode = this.highResDecompressionTree;
    this.decompressedValue = 0;
    this.decodingX = true;
    this.xSign = 1;
    this.ySign = 1;
    this.prevCoordinate = new ScriptelCoordinate();
    this.newCoordinate = new ScriptelCoordinate();
    this.absoluteCoordinate = new ScriptelCoordinate(0, 0);
    this.compressedStringBuilder = "";
    this.newStrokeFound = false;

    /**
     * A static place to hold a line segment to avoid new'ing one for every point
     * @private
     * @instance
     */
    this.segment = new ScriptelSignature();
    this.segment.strokes[0] = [];

    //Keyboard handler to register.
    var t = this;
    this.keyEventHandler = function (evt) {
        t.keyboardHandler(evt);
    };


    this.REPEAT_HALF = 59;
    this.NEW_STROKE_CODE = 60;
    this.REVERSE_X_CODE = 61;
    this.REVERSE_Y_CODE = 62;
    this.REVERSE_X_AND_Y_CODE = 63;
    this.REVERSE_X_CODE_SWAP = 3001;
    this.REVERSE_Y_CODE_SWAP = 3002;
    this.REVERSE_X_AND_Y_CODE_SWAP = 3003;
    this.REPEAT_HALF_SWAP = 3004;
    this.NEW_STROKE_CODE_SWAP = 3005;
    this.CANCEL_CODE = 3007;

    this.internationalization = new ScriptelInternationalization(this.fromKeyboardSpec);
    this.intlBeacon = [];
    this.intlStartChars = false;
}

/**
 * This method takes a buffer and attempts to parse it as either a Signature or
 * a card swipe, depending on what protocol it matches.
 * @method
 * @private
 * @param {string} buffer Captured string buffer.
 * @returns {ScriptelSignature|ScriptelCardSwipe} Returns either a signature or card swipe object, depending on the contents of the buffer.
 */
ScriptelEasyScript.prototype.parseEvent = function (buffer) {
    "use strict";
    //Need to determine if this is a card swipe or signature.
    var chr = buffer.charAt(0);
    if (chr === this.signatureProtocol.startStream) {
        return this.parseSignature(buffer);
    }

    return this.parseCard(buffer);
};

/**
 * This method attempts to parse a card swipe using the registered card
 * swipe protocol.
 * @method
 * @public
 * @param {string} buffer Card swipe string from the EasyScript device.
 * @returns {ScriptelCardSwipe} Parsed card swipe data.
 */
ScriptelEasyScript.prototype.parseCard = function (buffer) {
    "use strict";
    var p = this.cardSwipeProtocol;

    if (buffer.charAt(0) !== p.startStream) {
        throw new SignatureError("Card swipe stream doesn't start with the proper start character: " + buffer.charAt(0) + "!=" + p.startStream, 0);
    }
    if (buffer.charAt(buffer.length - 1) !== p.endStream) {
        throw new SignatureError("Card swipe stream doesn't end with the proper end character: " + buffer.charAt(buffer.length - 1) + "!=" + p.endStream, (buffer.length - 1));
    }
    if (buffer.substring(1, p.sentinel.length + 1).toLowerCase() !== p.sentinel.toLowerCase()) {
        throw new SignatureError("Card swipe stream doesn't start with the correct sentinel.", 1);
    }
    var result = new ScriptelCardSwipe();
    result.protocolVersion = buffer.charAt(8).toUpperCase();
    result.cardData = buffer.substring(10);

    //Check to see if this card is an identification card, if so
    //lets try to parse deeper.
    var i1 = this.parseIdentificationTrackOne(result.cardData);
    var i2 = this.parseIdentificationTrackTwo(result.cardData);
    var i3 = this.parseIdentificationTrackThree(result.cardData);
    if (i1 || i2 || i3) {
        //This appears to be an identification card.
        result.identificationCard = new ScriptelIdentificationCard();
        result.identificationCard.trackOne = i1;
        result.identificationCard.trackTwo = i2;
        result.identificationCard.trackThree = i3;
    }

    //Check to see if this card is a financial card, if so lets
    //try to parse deeper.
    var f1 = this.parseFinancialTrackOne(result.cardData);
    var f2 = this.parseFinancialTrackTwo(result.cardData);
    if (f1 || f2) {
        //This is a financial card.
        result.financialCard = new ScriptelFinancialCard();
        result.financialCard.trackOne = f1;
        result.financialCard.trackTwo = f2;

        var cardNumber = f1 ? f1.accountNumber : f2.accountNumber;
        result.financialCard.cardIssuer = this.checkFinancialCardType(cardNumber);
        result.financialCard.numberValid = this.checkFinancialCardChecksum(cardNumber);
    }

    return result;
};

/**
 * This method uses Luhn's algorithm to verify the checksum on a financial
 * card identification number.
 * @method
 * @public
 * @param {string} cardNumber The identification number of a financial card.
 * @returns {boolean} True if the number passes the checksum, false otherwise.
 */
ScriptelEasyScript.prototype.checkFinancialCardChecksum = function (cardNumber) {
    "use strict";
    var sum = 0, flip = 0, i;
    var table = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]];
    var index = 0;
    for (i = cardNumber.length - 1; i >= 0; i--) {
        index = flip++ & 1;
        sum += table[index][parseInt(cardNumber.charAt(i), 10)];
    }
    return sum % 10 === 0;
};

/**
 * This method uses pattern matching to attempt to identify the issuer for
 * a financial card.
 * @method
 * @public
 * @param {string} cardNumber The identification number of a financial card.
 * @returns {string} String describing the card issuer, false if no matches are found.
 */
ScriptelEasyScript.prototype.checkFinancialCardType = function (cardNumber) {
    "use strict";
    var cardTypes = {
        "American Express": /^(34|37)\d{13}$/,
        "Diners Club": /^(30[0-5]\d{11}|36\d{12})$/,
        "Carte Blanche": /^38\d{12}$/,
        "Discover": /^6011\d{12}$/,
        "EnRoute": /^(2131|1800)\d{11}$/,
        "JCB": /^(3\d{15}|(2131|1800)\d{11})$/,
        "Master Card": /^5[1-5]\d{14}$/,
        "Visa": /^4\d{12,15}$/
    };

    var type;
    var regex;
    for (type in cardTypes) {
        if (cardTypes.hasOwnProperty(type)) {
            regex = cardTypes[type];
            if (cardNumber.match(regex)) {
                return type;
            }
        }
    }
    return false;
};

/**
 * This method attempts to parse track one of a financial card based on the specification
 * outlined here: http://en.wikipedia.org/wiki/Magnetic_stripe_card.
 * Since there are many, many issuers this algorithm may not work for all financial cards.
 * @method
 * @public
 * @param {string} trackData The track data to parse.
 * @return {FinancialTrackOneData} Parsed track information on success, false on error.
 */
ScriptelEasyScript.prototype.parseFinancialTrackOne = function (trackData) {
    "use strict";
    var regex = /%[A-Z]{1}(\d{1,19})\^([^\^]{1,30})\^(\d{2})(\d{2})([0-9]{3})([A-Za-z 0-9]*)\?/;
    var match = trackData.match(regex);
    if (match) {
        var data = new FinancialTrackOneData();
        data.accountNumber = match[1];

        var expMonth = parseInt(match[4], 10);
        var expYear = parseInt(match[3], 10) + 2000;
        var expTemp = new Date(expYear, expMonth, 0, 0, 0, 0, 0);
        data.expiration = new Date();
        data.expiration.setTime(expTemp.getTime() - 1);

        data.serviceCode = match[5];
        data.discretionaryData = match[6];

        var name = match[2];
        var idx;
        idx = name.indexOf("/");
        if (idx >= 0) {
            data.lastName = name.substring(0, idx).trim();
            data.firstName = name.substring(idx + 1).trim();
        } else {
            data.firstName = name;
            data.lastName = "";
        }
        return data;
    }
    return false;
};

/**
 * This method attempts to parse track two of a financial card based on the specification
 * outlined here: http://en.wikipedia.org/wiki/Magnetic_stripe_card.
 * Since there are many, many issuers this algorithm may not work for all financial cards.
 * @method
 * @public
 * @param {string} trackData The track data to parse.
 * @return {FinancialTrackTwoData} Parsed track information on success, false on error.
 */
ScriptelEasyScript.prototype.parseFinancialTrackTwo = function (trackData) {
    "use strict";
    var regex = /;(\d{1,19})=(\d{2})(\d{2})(\d{3})([A-Za-z 0-9]*)\?/;
    var match = trackData.match(regex);
    if (match) {
        var data = new FinancialTrackTwoData();
        data.accountNumber = match[1];
        var expMonth = parseInt(match[3], 10);
        var expYear = parseInt(match[2], 10) + 2000;
        var expTemp = new Date(expYear, expMonth, 0, 0, 0, 0, 0);
        data.expiration = new Date();
        data.expiration.setTime(expTemp.getTime() - 1);

        data.serviceCode = match[4];
        data.discretionaryData = match[5];
        return data;
    }
    return false;
};

/**
 * This method attempts to parse track one of an identification card based on the specification
 * outlined here: http://www.aamva.org/?aspxerrorpath=/NR/rdonlyres/66260AD6-64B9-45E9-A253-B8AA32241BE0/0/2005DLIDCardSpecV2FINAL.pdf
 * Since many states issue these cards this may not work for all states with magnetic stripe data.
 * @method
 * @public
 * @param {string} trackData The track data to parse.
 * @return {IdentificationTrackOneData} Parsed track information on success, false on error.
 */
ScriptelEasyScript.prototype.parseIdentificationTrackOne = function (trackData) {
    "use strict";
    var regex = /\%([A-Z]{2})([A-Z\.\-' ]{1,13})\^?([A-Z\.\-' \$]{1,35})\^?([^\?\^]{1,29})\^?\?/;
    var match = trackData.match(regex);
    if (match) {
        var data = new IdentificationTrackOneData();
        data.state = match[1];
        data.city = match[2];

        var bits;
        if (match[3].indexOf("$") >= 0) {
            bits = match[3].split("$");
            data.lastName = bits[0];
            if (bits[1]) {
                data.firstName = bits[1];
            }
            if (bits[2]) {
                data.middleName = bits[2];
            }
        } else {
            bits = match[3].split(" ", 3);
            data.firstName = bits[0];
            if (bits[1]) {
                data.middleName = bits[1];
            }
            if (bits[2]) {
                data.lastName = bits[2];
            }
        }

        data.homeAddress = match[4];

        return data;
    }
    return false;
};

/**
 * This method attempts to parse track two of an identification card based on the specification
 * outlined here: http://www.aamva.org/?aspxerrorpath=/NR/rdonlyres/66260AD6-64B9-45E9-A253-B8AA32241BE0/0/2005DLIDCardSpecV2FINAL.pdf
 * Since many states issue these cards this may not work for all states with magnetic stripe data.
 * @method
 * @public
 * @param {string} trackData The track data to parse.
 * @return {IdentificationTrackTwoData} Parsed track information on success, false on error.
 */
ScriptelEasyScript.prototype.parseIdentificationTrackTwo = function (trackData) {
    "use strict";
    var regex = /;(6[0-9]{5})([0-9]{1,13})=([0-9]{4})([0-9]{8})([0-9]{0,5})=?\?/;
    var match = trackData.match(regex);
    if (match) {
        var data = new IdentificationTrackTwoData();
        data.issuerNumber = match[1];
        data.idNumber = match[2] + match[5];

        var expYear = 2000 + parseInt(match[3].substring(0, 2), 10);
        var expMonth = parseInt(match[3].substring(2), 10);
        var expTemp = new Date(expYear, expMonth, 1, 0, 0, 0, 0);
        data.expiration = new Date();
        data.expiration.setTime(expTemp.getTime() - 1);

        var birthYear = parseInt(match[4].substring(0, 4), 10);
        var birthMonth = parseInt(match[4].substring(4, 6), 10) - 1;
        var birthDay = parseInt(match[4].substring(6), 10);

        data.birthDate = new Date(birthYear, birthMonth, birthDay, 0, 0, 0, 0);

        return data;
    }

    return false;
};

/**
 * This method attempts to parse track three of an identification card based on the specification
 * outlined here: http://www.aamva.org/?aspxerrorpath=/NR/rdonlyres/66260AD6-64B9-45E9-A253-B8AA32241BE0/0/2005DLIDCardSpecV2FINAL.pdf
 * Since many states issue these cards this may not work for all states with magnetic stripe data.
 * @method
 * @public
 * @param {string} trackData The track data to parse.
 * @return {IdentificationTrackThreeData} Parsed track information on success, false on error.
 */
ScriptelEasyScript.prototype.parseIdentificationTrackThree = function (trackData) {
    "use strict";
    var regex = /%(.{1})([0-9 ]{1})([A-Z 0-9]{11})([A-Z 0-9]{2})([A-Z 0-9]{10})([A-Z 0-9]{4})(1|2|M|F)([0-9 ]{3})([0-9 ]{3})([A-Z ]{3})([A-Z ]{3})([^\?]{0,37})\?/;
    var match = trackData.match(regex);
    if (match) {
        var data = new IdentificationTrackThreeData();
        data.cdsVersion = parseInt(match[1], 10);
        data.jurisdictionVersion = parseInt(match[2], 10);
        data.zipCode = match[3].trim();
        data.licenseClass = match[4].trim();
        data.restrictions = match[5].trim();
        data.endorsements = match[6].trim();
        data.sex = (parseInt(match[7], 10) === 1 || match[7] === "M") ? "M" : "F";
        data.height = match[8].trim();
        data.weight = match[9].trim();
        data.hairColor = match[10].trim();
        data.eyeColor = match[11].trim();
        data.discretionaryData = match[12].trim();
        return data;
    }

    return false;
};

/**
* Given one character returns it's value according to the ScriptelEasyScript signatureProtocol.
* The function does not care if it finds it in the X or the Y table. The MSDs are multiplied by 23.
* @mehtod
* @private
* @param c {string} keyboarded encoded character representing a single character in the compressed stream
*/
ScriptelEasyScript.prototype.getTableValue = function (c) {
    "use strict";
    var i;
    var x = this.signatureProtocol.valueTable.x[0];
    var y = this.signatureProtocol.valueTable.y[0];
    var v = c.charCodeAt(0);
    for (i = 0; i < x.length; i++) {
        if ((v === x[i]) || (v === y[i])) {
            return i * 23;
        }
    }
    x = this.signatureProtocol.valueTable.x[1];
    y = this.signatureProtocol.valueTable.y[1];
    for (i = 0; i < x.length; i++) {
        if ((v === x[i]) || (v === y[i])) {
            return i;
        }
    }
    throw new SignatureError("Invalid keyboard code detected in compression stream C=" + c, this.parserStringPosition);
};

/**
* Given one characters at a time of the data portion of a compressed signature,
* decompresses it and the appropriate callbacks are made.
* @mehtod
* @private
* @param c {string} keyboarded encoded character representing a single character in the compressed stream
*/
ScriptelEasyScript.prototype.decompressStream = function (c) {
    "use strict";

    var value;

    this.compressedStringBuilder += c;

    if (this.compressedStringBuilder.length < 2) {
        return;
    }

    value = this.getTableValue(this.compressedStringBuilder[0]);
    value += this.getTableValue(this.compressedStringBuilder[1]);
    if (this.compressedStringBuilder.length === 3) {
        value += this.getTableValue(this.compressedStringBuilder[2]);
    } else if (value === 505) {
        return;
    }
    this.sendToAllListeners(new ScriptelOriginalString(this.compressedStringBuilder));
    this.compressedStringBuilder = "";

    if (value > 511) {
        throw new SignatureError("Invalid compressed signature, value > 511: " + value, this.parserStringPosition);
    }

    this.decompress(value);
};

/**
 * Decompresses a bit stream of numbers, each number representing 9 bits.
 * The bitstream is Modified Huffman compression.
 * @method
 * @private
 * @param bits {int} numbers in the range of 0 to 511, msb first, 9-bit portion of the compressed bit stream.
 */
ScriptelEasyScript.prototype.decompress = function (bits) {
    "use strict";
    var i, b;
    for (i = 256; i > 0; i >>>= 1) {
        b = bits & i;
        if (b === 0) {
            this.currentNode = this.currentNode.left;
        } else {
            this.currentNode = this.currentNode.right;
        }
        if (this.currentNode.value !== null) {
            if (this.currentNode.value < 64) {  // terminator code
                this.dataDecode(this.decompressedValue + this.currentNode.value);
                this.decompressedValue = 0;
            } else {                            // makeup code
                this.decompressedValue = this.currentNode.value;
            }
            this.currentNode = this.highResDecompressionTree;
        }
    }
};

/**
 * Decodes a stream of values and turns them into coordinates or commands.
 * @private
 * @method
 * @param value {int} a value from 0-3007 representing either delta values or commands to interpret them
 */
ScriptelEasyScript.prototype.dataDecode = function (value) {
    "use strict";

    if (this.decodingX) {
        switch (value) {
        case this.NEW_STROKE_CODE:
            this.newStrokeFound = true;
            break;
        case this.CANCEL_CODE:
            this.sendToAllListeners(new ScriptelCancelSignature());
            this.parserState = undefined;
            break;
        case this.REVERSE_X_CODE:
            this.xSign = -this.xSign;
            break;
        case this.REVERSE_Y_CODE:
            this.ySign = -this.ySign;
            break;
        case this.REVERSE_X_AND_Y_CODE:
            this.xSign = -this.xSign;
            this.ySign = -this.ySign;
            break;
        case this.REPEAT_HALF:
            this.newCoordinate.x = this.prevCoordinate.x;
            this.decodingX = false;
            break;
        case this.REPEAT_HALF_SWAP:
            this.newCoordinate.x = this.REPEAT_HALF;
            this.decodingX = false;
            break;
        case this.REVERSE_X_CODE_SWAP:
            this.newCoordinate.x = this.REVERSE_X_CODE;
            this.decodingX = false;
            break;
        case this.REVERSE_Y_CODE_SWAP:
            this.newCoordinate.x = this.REVERSE_Y_CODE;
            this.decodingX = false;
            break;
        case this.REVERSE_X_AND_Y_CODE_SWAP:
            this.newCoordinate.x = this.REVERSE_X_AND_Y_CODE;
            this.decodingX = false;
            break;
        case this.NEW_STROKE_CODE_SWAP:
            this.newCoordinate.x = this.NEW_STROKE_CODE;
            this.decodingX = false;
            break;
        default:
            this.newCoordinate.x = value;
            this.decodingX = false;
            break;
        }
    } else {
        this.prevCoordinate.x = this.newCoordinate.x;
        this.newCoordinate.x *= this.xSign;
        switch (value) {
        case this.REPEAT_HALF:
            this.newCoordinate.y = this.prevCoordinate.y;
            break;
        case this.REPEAT_HALF_SWAP:
            this.newCoordinate.y = this.REPEAT_HALF;
            break;
        default:
            this.newCoordinate.y = value;
            this.prevCoordinate.y = this.newCoordinate.y;
            break;
        }
        this.newCoordinate.y *= this.ySign;
        if (this.newStrokeFound) {
            this.sendToAllListeners(new ScriptelNewStroke());
            this.newStrokeFound = false;
        }
        this.absolute(this.newCoordinate);
        this.newCoordinate = new ScriptelCoordinate();
        this.decodingX = true;
    }
};

/**
 * Converts a relative point to an absolute point.
 * @private
 * @method
 * @param {ScriptelCoordinate} a point relative to the previous point.
 */
ScriptelEasyScript.prototype.absolute = function (relativeCoordinate) {
    "use strict";
    var max = 2999;
    var x, y;

    this.absoluteCoordinate.x += relativeCoordinate.x;
    this.absoluteCoordinate.y += relativeCoordinate.y;
    x = this.absoluteCoordinate.x / max * this.signatureProtocol.width;
    y = this.absoluteCoordinate.y / max * this.signatureProtocol.height;
    this.sendToAllListeners(new ScriptelCoordinate(x, y));
};


/**
 * This method parses an encoded ScripTouch series EasyScript
 * string and returns an object representing the decoded contents.
 * Errors are detected and exceptions are thrown if there is a
 * problem decoding the stream.
 * @method
 * @public
 * @param sig {string} Signature to parse.
 * @return {ScriptelSignature} Parsed signature.
 */
ScriptelEasyScript.prototype.parseSignature = function (sig) {
    "use strict";
    var result = new ScriptelSignature();
    var callback;

    //if (window.console && console.time) {
        //console.time("Parsing Signature");
    //}

    callback = this.registerStreamingCallback(function (e) {
        switch (e.type) {
        case "ScriptelSignatureMetaData":
            result.protocolVersion = e.protocolVersion;
            result.model = e.model;
            result.version = e.version;
            break;
        case "ScriptelNewStroke":
            result.strokes.push([]);
            break;
        case "ScriptelCoordinate":
            result.strokes[result.strokes.length - 1].push(e);
            break;
        case "ScriptelCancelSignature":
            result.strokes = [];
            break;
        }
    });

    this.parse(sig);
    this.unregisterStreamingCallback(callback);

    //if (window.console && console.time) {
        //console.timeEnd("Parsing Signature");
    //}
    this.sendToAllListeners(result, true);
    return result;
};

/**
 * This method uses the registered protocol to attempt to decode a single signature
 * point. This is only for uncompressed.
 * @private
 * @method
 * @param ePoint {string} Four encoded characters to attempt to match.
 * @param pos {number} The current position in the current stream, optional, used for error reporting.
 * @param abs {boolean} Return the integer points. Optional.
 * @param decode {boolean} Return the most and least significant bytes of the decoded value. Optional.
 * @return {ScriptelCoordinate} An object containing two members: x and y.
 * These members indicate the position of the point as a relative (percentage-based) position across the screen.
 */
ScriptelEasyScript.prototype.decodePoint = function (ePoint, pos) {
    "use strict";
    var d = {"x": [-1, -1], "y": [-1, -1]};

    var p = this.signatureProtocol;
    var i, axis, j, k, code, values;
    for (i = 0; i < ePoint.length; i++) {
        code = ePoint.charCodeAt(i);
        for (axis in p.valueTable) {
            if (p.valueTable.hasOwnProperty(axis)) {
                for (j = 0; j < p.valueTable[axis].length; j++) {
                    values = p.valueTable[axis][j];
                    for (k = 0; k < values.length; k++) {
                        if (values[k] === code) {
                            d[axis][j] = k;
                        }
                    }
                }
            }
        }
    }

    if (d.x[0] < 0 || d.x[1] < 0 || d.y[0] < 0 || d.y[1] < 0) {
        throw new SignatureError("Invalid point detected, missing code point: x1=" + d.x[0] + "  x2=" + d.x[1] + "  y1=" + d.y[0] + "  y2=" + d.y[1], pos);
    }

    //Constant, theoretical maximum coordinate.
    var max = (21 * 23) + 22;
    var retr = new ScriptelCoordinate(((d.x[0] * 23) + d.x[1]), ((d.y[0] * 23) + d.y[1]));

    //Change the integer values to floats representing percentages.
    retr.x = (retr.x / max) * this.signatureProtocol.width;
    retr.y = (retr.y / max) * this.signatureProtocol.height;

    return retr;
};

/**
 * This method adds a signature listener to an arbitrary DOM element.
 * The listener will listen for key presses and attempt to intercept
 * anything that looks like a signature stream. Most commonly the
 * listener will be attached to the document object.
 * @method
 * @public
 * @param e {DOMElement} Element from the DOM to attach the event listener to.
 */
ScriptelEasyScript.prototype.addSignatureListener = function (e) {
    "use strict";
    var t = this;
    if (e.addEventListener) {
        e.addEventListener("keydown", t.keyEventHandler , true);
    } else if (e.attachEvent) {
        e.attachEvent("onkeypress", t.keyEventHandler);
        e.attachEvent("onkeydown", t.keyEventHandler);
    }
};

/**
 * This method removes the event listener from a specified DOM object.
 * This is useful if you're no longer interested in intercepting signatures.
 * @method
 * @public
 * @param e {DOMElement} Element from the DOM to detach the event listener from.
 */
ScriptelEasyScript.prototype.removeSignatureListener = function (e) {
    "use strict";
    if (e.removeEventListener) {
		e.removeEventListener("keydown", this.keyEventHandler, true);
    } else if (e.attachEvent) {
        e.detachEvent("onkeypress", this.keyEventHandler);
        e.detachEvent("onkeyup", this.keyEventHandler);
    }
};

/**
 * This method registers a callback function that will be called when a
 * registered signature listener intercepts and successfully parses
 * an entire signature stream or card data.
 * The function will be passed a ScriptelSignature object or a
 * ScriptelCardSwipe object.
 * @method
 * @public
 * @param f {function} Function to be called upon receipt of a signature.
 * @return the function passed in
 */
ScriptelEasyScript.prototype.registerSignatureCallback = function (f) {
    "use strict";
    this.signatureCallbacks[this.signatureCallbacks.length] = f;

    if (this.signatureCallbacks.length === 1) {
        var t = this;
        this.legacyCallback = function (e) {
            switch (e.type) {
            case "ScriptelSignatureMetaData":
                this.legacySignature = new ScriptelSignature();
                this.legacySignature.protocolVersion = e.protocolVersion;
                this.legacySignature.model = e.model;
                this.legacySignature.version = e.version;
                if (this.legacySignature.protocolVersion === "A") {
                    this.useTimeout = true;
                }
                break;
            case "ScriptelNewStroke":
                this.legacySignature.strokes.push([]);
                break;
            case "ScriptelCoordinate":
                this.legacySignature.strokes[this.legacySignature.strokes.length - 1].push(e);
                break;
            case "ScriptelCancelSignature":
                this.legacySignature = null;
                this.useTimeout = false;
                break;
            case "ScriptelSignatureComplete":
                t.sendToAllListeners(this.legacySignature, true);
                this.useTimeout = false;
                break;
            }
        };
		this.registerStreamingCallback(this.legacyCallback);
    }
    return f;
};

/**
 * This method unregisters a signature callback.
 * @method
 * @public
 * @param f {function} Function to unregister.
 */
ScriptelEasyScript.prototype.unregisterSignatureCallback = function (f) {
    "use strict";
    var x;
    for (x = this.signatureCallbacks.length - 1; x >= 0; x--) {
        if (this.signatureCallbacks[x] === f) {
            this.signatureCallbacks.splice(x, 1);
        }
    }
    if (this.signatureCallbacks.length === 0) {
        this.unregisterStreamingCallback(this.legacyCallback);
        this.legacyCallback = undefined;
        this.legactSignature = undefined;
    }
};

/**
 * This method registers a callback function that will be called when a
 * registered signature listener intercepts and successfully parses
 * a piece of signature data. The function will be passed one of the
 * following object types: ScriptelSignatureMetaData, ScriptelCoordinate,
 * ScriptelNewStroke, ScriptelCancelSignature, ScriptelSignatureComplete
 * 
 * @method
 * @public
 * @param f {function} Function to be called upon receipt of a signature.
 */
ScriptelEasyScript.prototype.registerStreamingCallback = function (f) {
    "use strict";
    this.streamingCallbacks[this.streamingCallbacks.length] = f;
};

/**
 * This method unregisters a callback.
 * @method
 * @public
 * @param f {function} Function to unregister.
 */
ScriptelEasyScript.prototype.unregisterStreamingCallback = function (f) {
    "use strict";
    var x;
    for (x = this.streamingCallbacks.length - 1; x >= 0; x--) {
        if (this.streamingCallbacks[x] === f) {
            this.streamingCallbacks.splice(x, 1);
        }
    }
};

/**
 * This method takes a ScriptelSignature and Canvas and attempts to draw
 * the signature on the canvas.
 * @method
 * @public
 * @param sig {ScriptelSignature} Signature to draw on the canvas.
 * @param canvas {DOMElement} DOM element pointing to a canvas that the signature should be drawn on.
 * @param style {object<string,string>} Map of attributes to set on the canvas. For example strokeStyle:blue
 * will change the line color to blue. For a list of other attributes see: {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#Attributes}.
 */
ScriptelEasyScript.prototype.drawSignatureOnCanvas = function (sig, canvas, style) {
    "use strict";
    if (!sig || !canvas) {
        return;
    }

    if (!style) {
        style = {"strokeStyle": "black", "lineWidth": 1};
    }

    //if (window.console && console.time) {
        //console.time("Drawing Signature");
    //}

    var scale = canvas.width / this.signatureProtocol.width;
    var ctx = canvas.getContext("2d");
    //Copy canvas style
    var key;
    for (key in style) {
        if (style.hasOwnProperty(key)) {
            ctx[key] = style[key];
        }
    }

    var i, j, pt;
    for (i = 0; i < sig.strokes.length; i++) {
        ctx.beginPath();
        //Move to the initial position.
        pt = sig.strokes[i][0];
        ctx.moveTo(pt.x * scale, pt.y * scale);
        for (j = 1; j < sig.strokes[i].length; j++) {
            pt = sig.strokes[i][j];
            //Stoke to the next point(s)
            ctx.lineTo(pt.x * scale, pt.y * scale);
        }
        ctx.stroke();
    }
    //if (window.console && console.timeEnd) {
        //console.timeEnd("Drawing Signature");
    //}
};

/**
 * This method takes two points from a signature and Canvas and attempts to draw
 * the signature on the canvas.
 * @method
 * @public
 * @param point1 {ScriptelCoordinate} one of the two points of the line segment
 * @param point2 {ScriptelCoordinate} one of the two points of the line segment
 * @param canvas {DOMElement} DOM element pointing to a canvas that the signature should be drawn on.
 * @param style {object<string,string>} Map of attributes to set on the canvas. For example strokeStyle:blue
 * will change the line color to blue. For a list of other attributes see: {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#Attributes}.
 */
ScriptelEasyScript.prototype.drawSegmentOnCanvas = function (point1, point2, canvas, style) {
    "use strict";
    this.segment.strokes[0][0] = point1;
    this.segment.strokes[0][1] = point2;
    this.drawSignatureOnCanvas(this.segment, canvas, style);
};

/**
 * This method works closely with parse and accepts one character of what might be card data after the start sentinal.
 * It expects the card start sentinal to be in cardStringBuilder.
 * If it turns out to be card data, the card data is processed and "Card processed" is returned. If it is not card data
 * "Not a card" returned and all chars that have been collected, including the start sentinal can be found in
 * cardStringBuilder. If it is still unsure whether it is card data, undefined is returned and another character is expected.
 * It assumes the start sentinal is in cardStringBuilder.
 * @method
 * @private
 * @param key a character from the device.
 */
ScriptelEasyScript.prototype.cardHandler = function (key) {
    "use strict";
    this.cardStringBuilder += key;
    switch (this.cardHandlerState) {
    case undefined:
        if (key === this.cardSwipeProtocol.sentinel[this.cardStringBuilder.length - 2]) {
            if (this.cardStringBuilder.length === this.signatureProtocol.sentinel.length) {
                this.cardHandlerState = "card data";
            }
        } else {
            return "Not a card";
        }
        break;
    case "card data":
        // we know it is card data, collect the remainder of it because it will always come all at once
        if (key === this.cardSwipeProtocol.endStream) {
            var card = this.parseCard(this.cardStringBuilder);
            this.sendToAllListeners(card, true);
            this.sendToAllListeners(card, false);
            this.cardHandlerState = undefined;
            return "Card processed";
        }
        break;
    }
};

/**
 * This methode works closely with parse to parse points of an uncompressed stream, one at a time.
 * @method
 * @private
 * @param a string containing one character from the scriptel device.
 */
ScriptelEasyScript.prototype.parseUncompressedChar = function (key) {
    "use strict";
    if (key === this.signatureProtocol.penUp) {
        if (this.parserStringBuilder.length === 0) {
            this.parserStringBuilder = "new stroke";
            return;
        }
        throw new SignatureError("New stroke found mid-point", this.parserStringPosition);
    }
    this.parserStringBuilder += key;
    if (this.parserStringBuilder.length === 4) {
        var point = this.decodePoint(this.parserStringBuilder, 0);
        this.sendToAllListeners(point);
        this.sendToAllListeners(new ScriptelOriginalString(this.parserStringBuilder));
        this.parserStringBuilder = "";
    }
};

/**
 * Sends the value to all listeners
 * @method
 * @private
 * @param value the value to send
 * @param legacySignatureCallback {bool} whether this is the old callback for cards and entire signatures or the signature events.
 */
ScriptelEasyScript.prototype.sendToAllListeners = function (value, legacySignatureCallback) {
    "use strict";
    var i;
    if (legacySignatureCallback) {
        for (i = 0; i < this.signatureCallbacks.length; i++) {
            this.signatureCallbacks[i](value);
        }
    } else {
        for (i = 0; i < this.streamingCallbacks.length; i++) {
            this.streamingCallbacks[i](value);
        }
    }
};

/**
 * This methode accepts one or more characters at time of a stream coming from the device.
 * When enough characters are received to know something, such as a point being known
 * or information about the protocol, any associated registered event is fired.
 * @method
 * @private
 * @param a string containing one or more characters from the scriptel device.
 */
ScriptelEasyScript.prototype.parse = function (key, keyCode) {
    "use strict";
    var i;
    var cardResult;

    if (typeof key !== "string") {
        throw new SignatureError("The parse methode only accepts strings", this.parserStringPosition);
    }

    if (key.length === 0) {
        throw new SignatureError("The parse methode received 0 characters", this.parserStringPosition);
    }

    // if there is more than one character call this function recursively one character at a time.
    if (key.length !== 1) {
        for (i = 0; i < key.length; i++) {
            this.parse(key[i]);
        }
        return;
    }

    if (this.intlStartChars === false) {
        this.intlStartChars = this.internationalization.getPossibilities(this.signatureProtocol.startStream);
    }

    this.parserStringPosition++;
    switch (this.parserState) {
    case undefined:
        if (this.intlStartChars.indexOf(key) >= 0) {
            this.parserState = "signature sentinel";
            this.parserStringBuilder = this.signatureProtocol.startStream;
            this.parserStringPosition = 1;
            this.intlBeacon = [];
            this.internationalization = new ScriptelInternationalization(this.fromKeyboardSpec);
        } else if (key === this.cardSwipeProtocol.startStream) {
            this.parserState = "card sentinel";
            this.cardStringBuilder = key;
            this.parserStringPosition = 1;
            this.cardHandlerState = undefined;
        }
        break;
    case "card sentinel":
    try {
        cardResult = this.cardHandler(key);
    } catch (e) {
        this.intercepting = false;
        this.parserState = undefined;
        throw e;
	} finally {
        if (cardResult === "Card processed") {
            this.intercepting = false;
            this.terminatingKey = true;
            this.parserState = undefined;
        } else if (cardResult === "Not a card") {
            this.intercepting = false;
            this.parserState = undefined;
        } 
    }
        break;
    case "signature sentinel":
        var sentinelPossibilities = this.internationalization.getPossibilities(this.signatureProtocol.sentinel[this.parserStringPosition - 2]);
        if (sentinelPossibilities.indexOf(key) >= 0) {
            if (this.parserStringPosition === this.signatureProtocol.sentinel.length + 1) {
                this.parserState = "protocol version penup";
            }
        } else {
            this.intercepting = false;
            this.parserState = undefined;
        }
        break;
    case "protocol version penup":
        if (key !== this.signatureProtocol.penUp) {
            throw new SignatureError("expected penup", this.parserStringPosition);
        }
        this.parserState = "protocol version";
        this.ScriptelSignatureMetaData = new ScriptelSignatureMetaData();
        break;
    case "protocol version":
        if (key !== this.signatureProtocol.penUp) {
            this.ScriptelSignatureMetaData.protocolVersion += key;
            if (this.ScriptelSignatureMetaData.protocolVersionPosition === 0) {
                this.ScriptelSignatureMetaData.protocolVersionPosition = this.parserStringPosition;
                this.ScriptelSignatureMetaData.protocolVersionKeyCode = keyCode;
            }
        } else {
            // We're moving on, update the translation table if we have a beacon.
            if (this.intlBeacon.length >= 5) {
                this.internationalization.buildTable(this.intlBeacon);
            }
            this.parserState = "model";
        }
        break;
    case "model":
        if (key !== this.signatureProtocol.penUp) {
            this.ScriptelSignatureMetaData.model += key;
            if (this.ScriptelSignatureMetaData.modelPosition === 0) {
                this.ScriptelSignatureMetaData.modelPosition = this.parserStringPosition;
            }
        } else {
            this.parserState = "firmware version";
        }
        break;
    case "firmware version":
        if (key !== this.signatureProtocol.penUp) {
            this.ScriptelSignatureMetaData.version += key;
            if (this.ScriptelSignatureMetaData.versionPosition === 0) {
                this.ScriptelSignatureMetaData.versionPosition = this.parserStringPosition;
            }
        } else {
            //Strip off the extra characters that can be added for identification.
            this.ScriptelSignatureMetaData.protocolVersion = this.ScriptelSignatureMetaData.protocolVersion.charAt(0);

            //Protocol version might need correction.
            if (this.ScriptelSignatureMetaData.protocolVersionKeyCode) {
                this.ScriptelSignatureMetaData.protocolVersion = this.internationalization.mapCharacter(this.ScriptelSignatureMetaData.protocolVersionKeyCode, true);
            }

            var signatureStringFragment = this.signatureProtocol.startStream + 
                                          this.signatureProtocol.sentinel + " " +
                                          this.ScriptelSignatureMetaData.protocolVersion + " " +
                                          this.ScriptelSignatureMetaData.model + " " +
                                          this.ScriptelSignatureMetaData.version;

            this.sendToAllListeners(this.ScriptelSignatureMetaData);
            this.sendToAllListeners(new ScriptelOriginalString(signatureStringFragment));

            if (this.ScriptelSignatureMetaData.protocolVersion === "A" ||
                    this.ScriptelSignatureMetaData.protocolVersion === "B" ||
                    this.ScriptelSignatureMetaData.protocolVersion === "C") {
                this.parserState = "uncompressed strokes";
                this.parserStringBuilder = "new stroke";
            } else if (this.ScriptelSignatureMetaData.protocolVersion === "D" ||
                    this.ScriptelSignatureMetaData.protocolVersion === "E") {
                this.parserState = "compressed strokes";
                this.compressedStringBuilder = "";
                this.currentNode = this.highResDecompressionTree;
                this.decompressedValue = 0;
                this.decodingX = true;
                this.xSign = 1;
                this.ySign = 1;
                this.prevCoordinate = new ScriptelCoordinate();
                this.newCoordinate = new ScriptelCoordinate();
                this.absoluteCoordinate = new ScriptelCoordinate(0, 0);
                this.parserStringBuilder = "new stroke";
                this.newStrokeFound = false;
            } else {
            	this.intercepting = false;
                this.parserState = undefined;
                throw new SignatureError("unknown protocol version: " + this.ScriptelSignatureMetaData.protocolVersion, this.ScriptelSignatureMetaData.protocolVersionPosition);
            }
        }
        break;
    case "uncompressed strokes":
        if (key === this.signatureProtocol.startStream) {           // new signature, assume previous was canceld
            this.sendToAllListeners(new ScriptelOriginalString(this.signatureProtocol.cancelStream));
            this.sendToAllListeners(new ScriptelCancelSignature());
            this.parserState = undefined;
            this.parserStringBuilder = "";
            this.parse(key);
            break;
        }
        if (this.parserStringBuilder === "new stroke" && !(key === this.signatureProtocol.endStream || key === this.signatureProtocol.cancelStream)) {
            this.sendToAllListeners(new ScriptelOriginalString(this.signatureProtocol.penUp));
            this.sendToAllListeners(new ScriptelNewStroke());
            this.parserStringBuilder = "";
        }
        if (key === this.signatureProtocol.endStream) {
            this.sendToAllListeners(new ScriptelOriginalString(key));
            this.sendToAllListeners(new ScriptelSignatureComplete());
            this.intercepting = false;
			this.terminatingKey = true;
            this.parserState = undefined;
        } else if (key === this.signatureProtocol.cancelStream) {
            this.sendToAllListeners(new ScriptelOriginalString(key));
            this.sendToAllListeners(new ScriptelCancelSignature());
            this.intercepting = false;
			this.terminatingKey = true;
            this.parserState = undefined;
        } else if (key === this.cardSwipeProtocol.startStream) {
            this.parserState = "card interrupting uncompressed";
            this.cardHandlerState = undefined;
            this.cardStringBuilder = key;
        } else {
            this.parseUncompressedChar(key);
        }
        break;
    case "card interrupting uncompressed":
        cardResult = this.cardHandler(key);
        if (cardResult === "Not a card") {
            for (i = 0; i < this.cardStringBuilder.length; i++) {
                key = this.cardStringBuilder.charAt(i);
                if (key === this.signatureProtocol.endStream) {
                    this.sendToAllListeners(new ScriptelSignatureComplete());
                    this.parserState = undefined;
                } else if (key === this.signatureProtocol.cancelStream) {
                    this.sendToAllListeners(new ScriptelCancelSignature());
                    this.parserState = undefined;
                } else {
                    this.parseUncompressedChar(key);
                }
            }
            this.parserState = "uncompressed strokes";
        } else if (cardResult === "Card processed") {
            this.parserState = "uncompressed strokes";
        }
        break;
    case "compressed strokes":
        if (key === this.signatureProtocol.startStream) {           // new signature, assume previous was cancelled.
            this.sendToAllListeners(new ScriptelOriginalString(this.signatureProtocol.cancelStream));
            this.sendToAllListeners(new ScriptelCancelSignature());
            this.parserState = undefined;
            this.parserStringBuilder = "";
            this.parse(key);
            break;
        }
        if (this.parserStringBuilder === "new stroke" && key !== this.signatureProtocol.endStream) {
            this.sendToAllListeners(new ScriptelOriginalString(this.signatureProtocol.penUp));
            this.sendToAllListeners(new ScriptelNewStroke());
            this.parserStringBuilder = "";
        }
        if (key === this.signatureProtocol.endStream) {
            this.sendToAllListeners(new ScriptelOriginalString(key));
            this.sendToAllListeners(new ScriptelSignatureComplete());
            this.intercepting = false;
			this.terminatingKey = true;
            this.parserState = undefined;
        } else if (key === this.signatureProtocol.cancelStream) {
            this.sendToAllListeners(new ScriptelOriginalString(key));
            this.sendToAllListeners(new ScriptelCancelSignature());
            this.intercepting = false;
			this.terminatingKey = true;
            this.parserState = undefined;
        } else if (key === this.cardSwipeProtocol.startStream) {
            this.parserState = "card interrupting decompression";
            this.cardHandlerState = undefined;
            this.cardStringBuilder = key;
        } else {
            this.decompressStream(key);
        }
        break;
    case "card interrupting decompression":
        cardResult = this.cardHandler(key);
        if (cardResult === "Not a card") {
            for (i = 0; i < this.cardStringBuilder.length; i++) {
                key = this.cardStringBuilder.charAt(i);
                if (key === this.signatureProtocol.endStream) {
                    this.sendToAllListeners(new ScriptelSignatureComplete());
                    this.parserState = undefined;
                } else if (key === this.signatureProtocol.cancelStream) {
                    this.sendToAllListeners(new ScriptelCancelSignature());
                    this.parserState = undefined;
                } else {
                    this.decompressStream(key);
                }
            }
            this.parserState = "compressed strokes";
        } else if (cardResult === "Card processed") {
            this.parserState = "compressed strokes";
        }
        break;
    }
};

/**
 * This method attempts to map an international keyboard press based
 * on browser events.
 */
ScriptelEasyScript.prototype.mapKey = function (evt) {
    "use strict";
    //This got really complicated, moved to somewhere better.
    if (this.intlStartChars === false) {
        this.intlStartChars = this.internationalization.getPossibilities(this.signatureProtocol.startStream);
    }

    return this.internationalization.translateKey(evt);
};

/**
 * This method is what gets attached as an event handler by addSignatureListener.
 * It listens for interesting key presses and will intercept anything that looks
 * like a signature stream preventing it from propagating to the browser if possible.
 * @method
 * @private
 * @param evt {Event} DOM Event containing information about the key press.
 */
ScriptelEasyScript.prototype.keyboardHandler = function (evt) {
    "use strict";
    var chr = this.mapKey(evt);
    if (!chr) {
        return;
    }

    //Allow the application using this library to reconfigure this module if we're not currently
    //capturing.
    if (typeof this.configurationCallback === "function" && !this.intercepting) {
        this.configurationCallback(evt, chr);
    }

    if (!this.intercepting && ((this.intlStartChars.indexOf(chr) >= 0) || (chr === this.cardSwipeProtocol.startStream))) {
        this.lastProtocol = (chr === this.cardSwipeProtocol.startStream) ? this.cardSwipeProtocol : this.signatureProtocol;
        this.intercepting = true;
        this.lastKey = Date.now();
        //if (window.console && console.time) {
            //console.time("Capturing Keyboard Events");
        //}
    }

    if (this.lastProtocol === this.cardSwipeProtocol && this.cardSwipeProtocol.passThrough === true && this.charCount === 10) {
        this.intercepting = false;
        this.charCount = 0;
    }

    if (this.intercepting) {
        if (this.useTimeout  && (Date.now() - this.lastKey > this.timeout)) {
            //We're no longer intercepting because the timeout has expired.
            this.intercepting = false;
            this.charCount = 0;
        } else {
            this.charCount++;
            if (this.lastKeySpace && chr !== " ") {
                this.charCount++;
            }
        }

        var keyCode;
        if (!evt.code) {
            keyCode = evt.which || evt.keyCode;
        }

        this.parse(chr, keyCode);

        //Prevent the event from firing further if we can.
        if ((this.intercepting || this.terminatingKey) && evt.cancelable) {
            evt.preventDefault();
			this.terminatingKey = false;
        }

        //This is a special case for internationalization, we need access to the raw underlying events.
        if (this.parserState === "protocol version" && this.ScriptelSignatureMetaData.protocolVersion.length > 1) {
            this.intlBeacon[this.intlBeacon.length] = keyCode;
        }

        this.lastKey = Date.now();
    }
};

/**
 * This method takes a signature and converts it to a Base64 encoded data url
 * containing a Scalable Vector Graphics image.
 * @method
 * @public
 * @param sig {ScriptelSignature} Signature to encode.
 * @param scaleFactor {number} Number by which to uniformally scale the signature up or down.
 */
ScriptelEasyScript.prototype.getBase64EncodedSVG = function (sig, scaleFactor, strokeWidth) {
    "use strict";
    if (strokeWidth === undefined) {
        strokeWidth = 1;
    }
    var width = this.signatureProtocol.width;
    var height = this.signatureProtocol.height;
    var scale = 1;
    if (scaleFactor) {
        scale = scaleFactor;
    }

    var round = function (number, decimals) {
        var multiplier = Math.pow(10, decimals);
        number *= multiplier;
        return Math.round(number) / multiplier;
    };

    var svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"" + (width * scaleFactor) + "\" height=\"" + (height * scaleFactor) + "\">\n";

    var i, j;
    svg += "<path d=\"";
    for (i = 0; i < sig.strokes.length; i++) {
        //Move to an absolute position
        svg += "M" +  round((sig.strokes[i][0].x * scale), 1) + " " + round((sig.strokes[i][0].y * scale), 1);
        for (j = 0; j < sig.strokes[i].length - 1; j++) {
            //This implementation uses relative paths, absolute paths would use L
            svg += "l" + round((sig.strokes[i][j + 1].x * scale) - (sig.strokes[i][j].x * scale), 1) + " " + round((sig.strokes[i][j + 1].y * scale) - (sig.strokes[i][j].y * scale), 1);
        }
    }
    svg += "\" style=\"stroke-linecap: round;fill:none;stroke-width:" + strokeWidth + ";\" stroke=\"#000000\" />";
    svg += "</svg>";
    svg = ScriptelEasyScript.btoa(svg);
    return "data:image/svg+xml;base64," + svg;

};

ScriptelEasyScript.btoa = function (data) {
    "use strict";
   /** @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 */
};

/**
 * Stub, no longer necessary, here for legecy reasons only.
 * @method
 * @public
 * @return {string} String containing the translated character.
 */
ScriptelEasyScript.prototype.translateCharacterTo = function (chr) {
    "use strict";
    return chr;
};

/**
 * This method gets the bounding box around the current signature.
 * @method
 * @public
 * @return {ScriptelBoundingBox} Bounding box describing the limits of the signature.
 */
ScriptelSignature.prototype.getBounds = function () {
    "use strict";
    var retr = {"x1": Infinity, "x2": -1, "y1": Infinity, "y2": -1, "width": 0, "height": 0};
    var i, j, p;
    for (i = 0; i < this.strokes.length; i++) {
        for (j = 0; j < this.strokes[i].length; j++) {
            p = this.strokes[i][j];

            retr.x1 = (p.x < retr.x1) ? p.x : retr.x1;
            retr.x2 = (p.x > retr.x2) ? p.x : retr.x2;

            retr.y1 = (p.y < retr.y1) ? p.y : retr.y1;
            retr.y2 = (p.y > retr.y2) ? p.y : retr.y2;
        }
    }

    retr.width = retr.x2 - retr.x1;
    retr.height = retr.y2 - retr.y1;

    return retr;
};

/**
 * This method takes a signature and crops it to fit in the smallest
 * amount of space possible.
 * @method
 * @public
 */
ScriptelSignature.prototype.crop = function () {
    "use strict";
    var bounds = this.getBounds();
    var i, j, p;
    for (i = 0; i < this.strokes.length; i++) {
        for (j = 0; j < this.strokes[i].length; j++) {
            p = this.strokes[i][j];
            p.x -= bounds.x1;
            p.y -= bounds.y1;
        }
    }
};
