var $serial = ( typeof ( exports ) !== 'undefined' ) ? exports : { } ;
( function ( ) {
"use strict" ;
// We need this so we can test locally and still save layouts to AWS
$serial . base _href = "http://www.keyboard-layout-editor.com" ;
// Helper to copy an object; doesn't handle loops/circular refs, etc.
function copy ( o ) {
if ( typeof ( o ) !== 'object' ) {
// primitive value
return o ;
} else if ( o instanceof Array ) {
// array
var result = [ ] ;
for ( var i = 0 ; i < o . length ; i ++ ) {
result [ i ] = copy ( o [ i ] ) ;
}
return result ;
} else {
// Object
var result = { } ;
for ( var prop in o ) {
result [ prop ] = copy ( o [ prop ] ) ;
}
return result ;
}
}
function isEmptyObject ( o ) {
for ( var prop in o )
return false ;
return true ;
}
function extend ( target , source ) {
target = target || { } ;
for ( var prop in source ) {
if ( typeof source [ prop ] === 'object' ) {
target [ prop ] = extend ( target [ prop ] , source [ prop ] ) ;
} else {
target [ prop ] = source [ prop ] ;
}
}
return target ;
}
// Map from serialized label position to normalized position,
// depending on the alignment flags.
var labelMap = [
//0 1 2 3 4 5 6 7 8 9 10 11 // align flags
[ 0 , 6 , 2 , 8 , 9 , 11 , 3 , 5 , 1 , 4 , 7 , 10 ] , // 0 = no centering
[ 1 , 7 , - 1 , - 1 , 9 , 11 , 4 , - 1 , - 1 , - 1 , - 1 , 10 ] , // 1 = center x
[ 3 , - 1 , 5 , - 1 , 9 , 11 , - 1 , - 1 , 4 , - 1 , - 1 , 10 ] , // 2 = center y
[ 4 , - 1 , - 1 , - 1 , 9 , 11 , - 1 , - 1 , - 1 , - 1 , - 1 , 10 ] , // 3 = center x & y
[ 0 , 6 , 2 , 8 , 10 , - 1 , 3 , 5 , 1 , 4 , 7 , - 1 ] , // 4 = center front (default)
[ 1 , 7 , - 1 , - 1 , 10 , - 1 , 4 , - 1 , - 1 , - 1 , - 1 , - 1 ] , // 5 = center front & x
[ 3 , - 1 , 5 , - 1 , 10 , - 1 , - 1 , - 1 , 4 , - 1 , - 1 , - 1 ] , // 6 = center front & y
[ 4 , - 1 , - 1 , - 1 , 10 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ] , // 7 = center front & x & y
] ;
var disallowedAlignmentForLabels = [
[ 1 , 2 , 3 , 5 , 6 , 7 ] , //0
[ 2 , 3 , 6 , 7 ] , //1
[ 1 , 2 , 3 , 5 , 6 , 7 ] , //2
[ 1 , 3 , 5 , 7 ] , //3
[ ] , //4
[ 1 , 3 , 5 , 7 ] , //5
[ 1 , 2 , 3 , 5 , 6 , 7 ] , //6
[ 2 , 3 , 6 , 7 ] , //7
[ 1 , 2 , 3 , 5 , 6 , 7 ] , //8
[ 4 , 5 , 6 , 7 ] , //9
[ ] , //10
[ 4 , 5 , 6 , 7 ] //11
] ;
// Lenient JSON reader/writer
$serial . toJsonL = function ( obj ) {
var res = [ ] , key ;
if ( obj instanceof Array ) {
obj . forEach ( function ( elem ) { res . push ( $serial . toJsonL ( elem ) ) ; } ) ;
return '[' + res . join ( ',' ) + ']' ;
}
if ( typeof obj === 'object' ) {
for ( key in obj ) { if ( obj . hasOwnProperty ( key ) ) { res . push ( key + ':' + $serial . toJsonL ( obj [ key ] ) ) ; } }
return '{' + res . join ( ',' ) + '}' ;
}
if ( typeof obj === 'number' ) {
return Math . round10 ( obj , - 4 ) ;
}
return angular . toJson ( obj ) ;
} ;
$serial . fromJsonL = function ( json ) { return jsonl . parse ( json ) ; } ;
// function to sort the key array
$serial . sortKeys = function ( keys ) {
keys . sort ( function ( a , b ) {
return ( ( a . rotation _angle + 360 ) % 360 - ( b . rotation _angle + 360 ) % 360 ) ||
( a . rotation _x - b . rotation _x ) ||
( a . rotation _y - b . rotation _y ) ||
( a . y - b . y ) ||
( a . x - b . x ) ;
} ) ;
} ;
var _defaultKeyProps = {
x : 0 , y : 0 , x2 : 0 , y2 : 0 , // position
width : 1 , height : 1 , width2 : 1 , height2 : 1 , // size
rotation _angle : 0 , rotation _x : 0 , rotation _y : 0 , // rotation
labels : [ ] , textColor : [ ] , textSize : [ ] , // label properties
default : { textColor : "#000000" , textSize : 3 } , // label defaults
color : "#cccccc" , profile : "" , nub : false , // cap appearance
ghost : false , stepped : false , decal : false , // miscellaneous options
sm : "" , sb : "" , st : "" // switch
} ;
var _defaultMetaData = { backcolor : '#eeeeee' , name : '' , author : '' , notes : '' , background : undefined , radii : '' , switchMount : '' , switchBrand : '' , switchType : '' } ;
$serial . defaultKeyProps = function ( ) { return copy ( _defaultKeyProps ) ; } ;
$serial . defaultMetaData = function ( ) { return copy ( _defaultMetaData ) ; } ;
function reorderLabels ( key , current ) {
// Possible alignment flags in order of preference (this is fairly
// arbitrary, but hoped to reduce raw data size).
var align = [ 7 , 5 , 6 , 4 , 3 , 1 , 2 , 0 ] ;
// remove impossible flag combinations
for ( var i = 0 ; i < key . labels . length ; ++ i ) {
if ( key . labels [ i ] ) {
align . remove . apply ( align , disallowedAlignmentForLabels [ i ] ) ;
}
}
// For the chosen alignment, generate the label array in the correct order
var ret = {
align : align [ 0 ] ,
labels : [ "" , "" , "" , "" , "" , "" , "" , "" , "" , "" , "" , "" ] ,
textColor : [ "" , "" , "" , "" , "" , "" , "" , "" , "" , "" , "" , "" ] ,
textSize : [ ]
} ;
for ( var i = 0 ; i < 12 ; ++ i ) {
var ndx = labelMap [ ret . align ] . indexOf ( i ) ;
if ( ndx >= 0 ) {
if ( key . labels [ i ] ) ret . labels [ ndx ] = key . labels [ i ] ;
if ( key . textColor [ i ] ) ret . textColor [ ndx ] = key . textColor [ i ] ;
if ( key . textSize [ i ] ) ret . textSize [ ndx ] = key . textSize [ i ] ;
}
}
// Clean up
for ( var i = 0 ; i < ret . textSize . length ; ++ i ) {
if ( ! ret . labels [ i ] )
ret . textSize [ i ] = current . textSize [ i ] ;
if ( ! ret . textSize [ i ] || ret . textSize [ i ] == key . default . textSize )
ret . textSize [ i ] = 0 ;
}
return ret ;
}
function compareTextSizes ( current , key , labels ) {
if ( typeof ( current ) === "number" )
current = [ current ] ;
for ( var i = 0 ; i < 12 ; ++ i ) {
if ( labels [ i ] && ( ( ! ! current [ i ] !== ! ! key [ i ] ) || ( current [ i ] && current [ i ] !== key [ i ] ) ) )
return false ;
}
return true ;
}
// Convert between our in-memory format & our serialized format
function serializeProp ( props , nname , val , defval ) { if ( val !== defval ) { props [ nname ] = val ; } return val ; }
$serial . serialize = function ( keyboard ) {
var keys = keyboard . keys ;
var rows = [ ] , row = [ ] ;
var current = $serial . defaultKeyProps ( ) ;
current . textColor = current . default . textColor ;
current . align = 4 ;
var cluster = { r : 0 , rx : 0 , ry : 0 } ;
// Serialize metadata
var meta = { } ;
for ( var metakey in keyboard . meta ) {
serializeProp ( meta , metakey , keyboard . meta [ metakey ] , _defaultMetaData [ metakey ] ) ;
}
if ( ! isEmptyObject ( meta ) ) {
rows . push ( meta ) ;
}
var newRow = true ;
current . y -- ; // will be incremented on first row
// Serialize row/key-data
$serial . sortKeys ( keys ) ;
keys . forEach ( function ( key ) {
var props = { } ;
var ordered = reorderLabels ( key , current ) ;
// start a new row when necessary
var clusterChanged = ( key . rotation _angle != cluster . r || key . rotation _x != cluster . rx || key . rotation _y != cluster . ry ) ;
var rowChanged = ( key . y !== current . y ) ;
if ( row . length > 0 && ( rowChanged || clusterChanged ) ) {
// Push the old row
rows . push ( row ) ;
row = [ ] ;
newRow = true ;
}
if ( newRow ) {
// Set up for the new row
current . y ++ ;
// 'y' is reset if *either* 'rx' or 'ry' are changed
if ( key . rotation _y != cluster . ry || key . rotation _x != cluster . rx )
current . y = key . rotation _y ;
current . x = key . rotation _x ; // always reset x to rx (which defaults to zero)
// Update current cluster
cluster . r = key . rotation _angle ;
cluster . rx = key . rotation _x ;
cluster . ry = key . rotation _y ;
newRow = false ;
}
current . rotation _angle = serializeProp ( props , "r" , key . rotation _angle , current . rotation _angle ) ;
current . rotation _x = serializeProp ( props , "rx" , key . rotation _x , current . rotation _x ) ;
current . rotation _y = serializeProp ( props , "ry" , key . rotation _y , current . rotation _y ) ;
current . y += serializeProp ( props , "y" , key . y - current . y , 0 ) ;
current . x += serializeProp ( props , "x" , key . x - current . x , 0 ) + key . width ;
current . color = serializeProp ( props , "c" , key . color , current . color ) ;
if ( ! ordered . textColor [ 0 ] ) {
ordered . textColor [ 0 ] = key . default . textColor ;
} else {
for ( var i = 2 ; i < 12 ; ++ i ) {
if ( ! ordered . textColor [ i ] && ordered . textColor [ i ] !== ordered . textColor [ 0 ] ) {
ordered . textColor [ i ] !== key . default . textColor ;
}
}
}
current . textColor = serializeProp ( props , "t" , ordered . textColor . join ( "\n" ) . trimEnd ( ) , current . textColor ) ;
current . ghost = serializeProp ( props , "g" , key . ghost , current . ghost ) ;
current . profile = serializeProp ( props , "p" , key . profile , current . profile ) ;
current . sm = serializeProp ( props , "sm" , key . sm , current . sm ) ;
current . sb = serializeProp ( props , "sb" , key . sb , current . sb ) ;
current . st = serializeProp ( props , "st" , key . st , current . st ) ;
current . align = serializeProp ( props , "a" , ordered . align , current . align ) ;
current . default . textSize = serializeProp ( props , "f" , key . default . textSize , current . default . textSize ) ;
if ( props . f ) current . textSize = [ ] ;
if ( ! compareTextSizes ( current . textSize , ordered . textSize , ordered . labels ) ) {
if ( ordered . textSize . length == 0 ) {
serializeProp ( props , "f" , key . default . textSize , - 1 ) ; // Force 'f' to be written
} else {
var optimizeF2 = ! ordered . textSize [ 0 ] ;
for ( var i = 2 ; i < ordered . textSize . length && optimizeF2 ; ++ i ) {
optimizeF2 = ( ordered . textSize [ i ] == ordered . textSize [ 1 ] ) ;
}
if ( optimizeF2 ) {
var f2 = ordered . textSize [ 1 ] ;
current . f2 = serializeProp ( props , "f2" , f2 , - 1 ) ;
current . textSize = [ 0 , f2 , f2 , f2 , f2 , f2 , f2 , f2 , f2 , f2 , f2 , f2 ] ;
} else {
current . f2 = undefined ;
current . textSize = serializeProp ( props , "fa" , ordered . textSize , [ ] ) ;
}
}
}
serializeProp ( props , "w" , key . width , 1 ) ;
serializeProp ( props , "h" , key . height , 1 ) ;
serializeProp ( props , "w2" , key . width2 , key . width ) ;
serializeProp ( props , "h2" , key . height2 , key . height ) ;
serializeProp ( props , "x2" , key . x2 , 0 ) ;
serializeProp ( props , "y2" , key . y2 , 0 ) ;
serializeProp ( props , "n" , key . nub || false , false ) ;
serializeProp ( props , "l" , key . stepped || false , false ) ;
serializeProp ( props , "d" , key . decal || false , false ) ;
if ( ! isEmptyObject ( props ) ) { row . push ( props ) ; }
current . labels = ordered . labels ;
row . push ( ordered . labels . join ( "\n" ) . trimEnd ( ) ) ;
} ) ;
if ( row . length > 0 ) {
rows . push ( row ) ;
}
return rows ;
}
function deserializeError ( msg , data ) {
throw "Error: " + msg + ( data ? ( ":\n " + $serial . toJsonL ( data ) ) : "" ) ;
}
function reorderLabelsIn ( labels , align , skipdefault ) {
var ret = [ ] ;
for ( var i = skipdefault ? 1 : 0 ; i < labels . length ; ++ i ) {
ret [ labelMap [ align ] [ i ] ] = labels [ i ] ;
}
return ret ;
}
$serial . deserialize = function ( rows ) {
// Initialize with defaults
var current = $serial . defaultKeyProps ( ) ;
var meta = $serial . defaultMetaData ( ) ;
var keys = [ ] ;
var cluster = { x : 0 , y : 0 } ;
var align = 4 ;
for ( var r = 0 ; r < rows . length ; ++ r ) {
if ( rows [ r ] instanceof Array ) {
for ( var k = 0 ; k < rows [ r ] . length ; ++ k ) {
var key = rows [ r ] [ k ] ;
if ( typeof key === 'string' ) {
var newKey = copy ( current ) ;
newKey . width2 = newKey . width2 === 0 ? current . width : current . width2 ;
newKey . height2 = newKey . height2 === 0 ? current . height : current . height2 ;
newKey . labels = reorderLabelsIn ( key . split ( '\n' ) , align ) ;
newKey . textSize = reorderLabelsIn ( newKey . textSize , align ) ;
// Clean up the data
for ( var i = 0 ; i < 12 ; ++ i ) {
if ( ! newKey . labels [ i ] ) {
newKey . textSize [ i ] = undefined ;
newKey . textColor [ i ] = undefined ;
}
if ( newKey . textSize [ i ] == newKey . default . textSize )
newKey . textSize [ i ] = undefined ;
if ( newKey . textColor [ i ] == newKey . default . textColor )
newKey . textColor [ i ] = undefined ;
}
// Add the key!
keys . push ( newKey ) ;
// Set up for the next key
current . x += current . width ;
current . width = current . height = 1 ;
current . x2 = current . y2 = current . width2 = current . height2 = 0 ;
current . nub = current . stepped = current . decal = false ;
} else {
if ( key . r != null ) { if ( k != 0 ) { deserializeError ( "'r' can only be used on the first key in a row" , key ) ; } current . rotation _angle = key . r ; }
if ( key . rx != null ) { if ( k != 0 ) { deserializeError ( "'rx' can only be used on the first key in a row" , key ) ; } current . rotation _x = cluster . x = key . rx ; extend ( current , cluster ) ; }
if ( key . ry != null ) { if ( k != 0 ) { deserializeError ( "ry' can only be used on the first key in a row" , key ) ; } current . rotation _y = cluster . y = key . ry ; extend ( current , cluster ) ; }
if ( key . a != null ) { align = key . a ; }
if ( key . f ) { current . default . textSize = key . f ; current . textSize = [ ] ; }
if ( key . f2 ) { for ( var i = 1 ; i < 12 ; ++ i ) { current . textSize [ i ] = key . f2 ; } }
if ( key . fa ) { current . textSize = key . fa ; }
if ( key . p ) { current . profile = key . p ; }
if ( key . c ) { current . color = key . c ; }
if ( key . t ) {
var split = key . t . split ( '\n' ) ;
current . default . textColor = split [ 0 ] ;
current . textColor = reorderLabelsIn ( split , align ) ;
}
if ( key . x ) { current . x += key . x ; }
if ( key . y ) { current . y += key . y ; }
if ( key . w ) { current . width = current . width2 = key . w ; }
if ( key . h ) { current . height = current . height2 = key . h ; }
if ( key . x2 ) { current . x2 = key . x2 ; }
if ( key . y2 ) { current . y2 = key . y2 ; }
if ( key . w2 ) { current . width2 = key . w2 ; }
if ( key . h2 ) { current . height2 = key . h2 ; }
if ( key . n ) { current . nub = key . n ; }
if ( key . l ) { current . stepped = key . l ; }
if ( key . d ) { current . decal = key . d ; }
if ( key . g != null ) { current . ghost = key . g ; }
if ( key . sm ) { current . sm = key . sm ; }
if ( key . sb ) { current . sb = key . sb ; }
if ( key . st ) { current . st = key . st ; }
}
}
// End of the row
current . y ++ ;
} else if ( typeof rows [ r ] === 'object' ) {
if ( r != 0 ) { throw "Error: keyboard metadata must the be first element:\n " + $serial . toJsonL ( rows [ r ] ) ; }
extend ( meta , rows [ r ] ) ;
}
current . x = current . rotation _x ;
}
return { meta : meta , keys : keys } ;
}
} ( ) ) ;