/*** KeymanWeb 2.0 Copyright 2014 Tavultesoft Pty Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ***/ /** * Base code: Declare tavultesoft, major component namespaces and instances, utility functions */ /** @define {number} */ var __BUILD__ = 300; (function() { // Define exposed base object var tavultesoft = window['tavultesoft'] = {}; var keymanweb = tavultesoft['keymanweb'] = { _TitleElement: null, // I1972 - KeymanWeb Titlebar should not be a link _Enabled: 1, // Is KeymanWeb running? _IE: 0, // browser version identification legacy: 0, // set true for IE 3,4,5 (I2186 - multiple IE tweaks needed) _IsActivatingKeymanWebUI: 0, // ActivatingKeymanWebUI - is the KeymanWeb DIV in process of being clicked on? _JustActivatedKeymanWebUI: 0, // JustActivatedKeymanWebUI - focussing back to control after KeymanWeb UI interaction _IgnoreNextSelChange: 0, // when a visual keyboard key is mouse-down, ignore the next sel change because this stuffs up our history _Selection: null, _SelectionControl: null, _KeyboardStubs: [], // KeyboardStubs - array of available keyboards dfltStub: null, // First keyboard stub loaded - default for touch-screen devices, ignored on desktops _Keyboards: [], // Keyboards - array of loaded keyboards _ActiveKeyboard: null, // ActiveKeyboard - points to active keyboard in Keyboards array _ActiveStub: null, // ActiveStub - points to active stub in KeyboardStubs _LoadingInternalName: null, _AnyIndices: [], // AnyIndex - array of any/index match indices _DeadKeys: [], // DeadKeys - array of matched deadkeys _ActiveControl: null, // Currently active control in _Controls array _Controls: [], // Array of controls with specific states: .Control, .Enabled, .DefaultInternalName _AttachedElements: [], // I1596 - attach to controls dynamically _ActiveElement: null, // Currently active (focused) element I3363 (Build 301) _LastActiveElement: null, // LastElem - Last active element _DfltStyle: '', // Default styles _MasterDocument: null, // Document with controller (to allow iframes to distinguish local/master control) _HotKeys: [], // Array of document-level hotkey objects focusTimer: null, // Timer to manage loss of focus to unmapped input focusing: false, // Flag to manage movement of focus resizing: false, // Flag to control resize events when resetting viewport parameters sortedInputs: [], // List of all INPUT and TEXTAREA elements ordered top to bottom, left to right inputList: [], // List of simulated input divisions for touch-devices I3363 (Build 301) languageList: null, // List of keyboard languages available for KeymanCloud languagesPending: [], // Array of languages waiting to be registered deferredStubs: [], // Array of pending keyboard stubs from addKeyboard(), to register after initialization deferredKRS: [], // Array of pending keyboard stubs from KRS, to register afterf initialization deferredKR: [], // Array of pending keyboards, to be installed at end of initialization loadTimer:null, // Keyboard loading timer, to manage load failure waiting:null, // Element displayed during keyboard load time warned: false, // Warning flag (to prevent multiple warnings) baseFont: 'sans-serif', // Default font for mapped input elements appliedFont: '', // Chain of fonts to be applied to mapped input elements embeddedFonts: [], // Array of currently embedded fonts fontCheckTimer: null, // Timer for testing loading of embedded fonts srcPath: '', // Path to folder containing executing keymanweb script rootPath: '', // Path to server root mustReloadKeyboard: false,// Force keyboard refreshing even if already loaded fullInitialization: true // Force full page initialization unless overridden }; keymanweb['initialized'] = 0; keymanweb['build'] = __BUILD__; keymanweb['version'] = '2.0'; keymanweb['helpURL'] = 'http://help.keyman.com/go'; // Expose (old) KeymanWeb object for use by compiled keyboards (replaces legacy object in earlier versions) window['KeymanWeb'] = keymanweb; // Define public OSK, user interface and utility function objects var util = keymanweb['util'] = {}; var osk = keymanweb['osk'] = {ready:false}; var ui = keymanweb['ui'] = {}; // Stub functions (defined later in code only if required) keymanweb.setDefaultDeviceOptions = function(opt){} keymanweb.getStyleSheetPath = function(s){return s;} keymanweb.getKeyboardPath=function(f){return f;} keymanweb.KC_ = function(n, ln, Pelem){return '';} keymanweb.handleRotationEvents = function(){} keymanweb.alignInputs = function(b){} keymanweb.refreshElementContent = null; osk.highlightSubKeys = function(k,x,y){} osk.createKeyTip = function(){} osk.optionKey = function(e,keyName,keyDown){} osk.showKeyTip = function(key,on){} osk.waitForFonts = function(kfd,ofd){return true;} // Define private options object with empty members keymanweb.options = { 'root':'', 'resources':'', 'keyboards':'', 'fonts':'', 'attachType':'', 'ui':null }; // Generalized component event registration util.events = []; util.currentEvents = []; /** * Determine path and protocol of executing script * * This can only be done during load when the active script will be the * last script loaded. Otherwise the script must be identified by name. */ var scripts = document.getElementsByTagName('script'); var ss = scripts[scripts.length-1].src, sPath = ss.substr(0,ss.lastIndexOf('/')+1); keymanweb.srcPath = sPath; keymanweb.rootPath = sPath.replace(/(https?:\/\/)([^\/]*)(.*)/,'$1$2/'); keymanweb.protocol = sPath.replace(/(.{3,5}:)(.*)/,'$1'); /* osk.positionChanged = function(newPosition) { return util.callEvent('osk.positionChanged', newPosition); } osk['setPosition'] = function(newPosition) { divOsk.left = newPosition.left; ... osk.positionChanged({left: divOsk.left, top: divOsk.top, ...}); } ui.oskPositionChanged = function(newPosition) { // do something with the osk } ui.init = function() { osk.addEventListener('positionChanged', ui.oskPositionChanged); } */ /** * Function addEventListener * Scope Private * @param {string} event name of event prefixed by module, e.g. osk.touchmove * @param {function(Object)} func event handler * @return {boolean} * Description Add (or replace) an event listener for this component */ util.addEventListener = function(event, func) { util.removeEventListener(event, func); util.events[event].push(func); return true; } /** * Function removeEventListener * Scope Private * @param {string} event name of event prefixed by module, e.g. osk.touchmove * @param {function(Object)} func event handler * @return {boolean} * Description Remove the specified function from the listeners for this event */ util.removeEventListener = function(event, func) { if(typeof util.events[event] == 'undefined') util.events[event] = []; for(var i=0; i 1) return dflt; else return ''; } /** * Function setOption * Scope Public * @param {string} option Name of option * @param {*=} value Value of option * Description Sets value of named option */ util['setOption'] = util.setOption = function(optionName,value) { keymanweb.options[optionName] = value; } /** * Function getAbsoluteX * Scope Public * @param {Object} Pobj HTML element * @return {number} * Description Returns x-coordinate of Pobj element absolute position with respect to page */ util['getAbsoluteX'] = util._GetAbsoluteX = function(Pobj) // I1476 - Handle SELECT overlapping END { if(!Pobj) return 0; var Lcurleft = Pobj.offsetLeft ? Pobj.offsetLeft : 0, Lobj = Pobj; // I2404 - Support for IFRAMEs if (Lobj.offsetParent) { while (Lobj.offsetParent) { Lobj = Lobj.offsetParent; Lcurleft += Lobj.offsetLeft; } } // Correct position if element is within a frame (but not if the controller is in document within that frame) if(Lobj && Lobj.ownerDocument && (Pobj.ownerDocument != keymanweb._MasterDocument)) Lobj=Lobj.ownerDocument; // I2404 - Support for IFRAMEs // The following two lines are old code and may or may not still be needed - possibly should be conditioned similalry to above if(Lobj && Lobj.parentWindow && Lobj.parentWindow.frameElement) return Lcurleft + util._GetAbsoluteX(Lobj.parentWindow.frameElement) - Lobj.documentElement.scrollLeft; if(Lobj && Lobj.defaultView && Lobj.defaultView.frameElement) return Lcurleft + util._GetAbsoluteX(Lobj.defaultView.frameElement) - Lobj.documentElement.scrollLeft; return Lcurleft; } /** * Function getAbsoluteY * Scope Public * @param {Object} Pobj HTML element * @return {number} * Description Returns y-coordinate of Pobj element absolute position with respect to page */ util['getAbsoluteY'] = util._GetAbsoluteY = function(Pobj) { if(!Pobj) return 0; var Lcurtop = Pobj.offsetTop ? Pobj.offsetTop : 0, Lobj = Pobj; // I2404 - Support for IFRAMEs if (Lobj.offsetParent) { while (Lobj.offsetParent) { Lobj = Lobj.offsetParent; Lcurtop += Lobj.offsetTop; } } // Correct position if element is within a frame (but not if the controller is in document within that frame) if(Lobj && Lobj.ownerDocument && (Pobj.ownerDocument != keymanweb._MasterDocument)) Lobj=Lobj.ownerDocument; // I2404 - Support for IFRAMEs // The following two lines are old code and may or may not still be needed - possibly should be conditioned similalry to above if(Lobj && Lobj.parentWindow && Lobj.parentWindow.frameElement) return Lcurtop + util._GetAbsoluteY(Lobj.parentWindow.frameElement) - Lobj.documentElement.scrollTop; if(Lobj && Lobj.defaultView && Lobj.defaultView.frameElement) return Lcurtop + util._GetAbsoluteY(Lobj.defaultView.frameElement) - Lobj.documentElement.scrollTop; return Lcurtop; } /** * Function getAbsolute * Scope Public * @param {Object} Pobj HTML element * @return {Object.} * Description Returns absolute position of Pobj element with respect to page */ util['getAbsolute'] = util._GetAbsolute = function(Pobj) { var p={}; p['x'] = util._GetAbsoluteX(Pobj); p['y'] = util._GetAbsoluteY(Pobj); return p; } /** * Select start handler (to replace multiple inline handlers) (Build 360) */ util.selectStartHandler = function() { return false; } /** * Default mouse down event handler (to replace multiple inline handlers) (Build 360) */ util.mouseDownPreventDefaultHandler = function(e) { if(e) e.preventDefault(); } util.mouseDownPreventDefaultHandler = function(e) { if(e) e.preventDefault(); } util['createElement'] = util._CreateElement = function(nodeName) { var e = document.createElement(nodeName); // Make element unselectable (Internet Explorer) if (typeof e.onselectstart != 'undefined') //IE route { e.unSelectable='on'; e.onselectstart=util.selectStartHandler; // Build 360 } // And for well-behaved browsers (may also work for IE9+, but not necessary) else { e.style.MozUserSelect="none"; e.style.KhtmlUserSelect="none"; e.style.UserSelect="none"; e.style.WebkitUserSelect="none"; } return e; } /** * Function getIEVersion * Scope Public * @return {number} * Description Return IE version number (or 999 if browser not IE) */ util['getIEVersion'] = util._GetIEVersion = function() { var n,agent=''; if('userAgent' in navigator) agent=navigator.userAgent; // Test first for old versions if('selection' in document) // only defined for IE and not for IE 11!!! { var appVer=navigator.appVersion; n=appVer.indexOf('MSIE '); if(n >= 0) { // Check for quirks mode page, always return 6 if so if(document.compatMode == 'BackCompat') return 6; appVer=appVer.substr(n+5); n=appVer.indexOf('.'); if(n > 0) return parseInt(appVer.substr(0,n),10); } } // Finally test for IE 11 (and later?) n=agent.indexOf('Trident/'); if(n < 0) return 999; agent=agent.substr(n+8); n=agent.indexOf('.'); if(n > 0) return parseInt(agent.substr(0,n),10)+4; return 999; } /** * Get device horizontal DPI for touch devices, to set actual size of active regions * Note that the actual physical DPI may be somewhat different. * * @return {number} */ util.getDPI = function() { var t=document.createElement('DIV'),s=t.style,dpi=96; if(document.readyState !== 'complete') return dpi; t.id='calculateDPI'; s.position='absolute'; s.display='block';s.visibility='hidden'; s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px'; document.body.appendChild(t); dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio; document.body.removeChild(t); return dpi; } /** * Get browser-independent computed style value for element * * @param {Element} e HTML element * @param {string} s CSS style name * @return {*} */ util.getStyleValue = function(e,s) { // Build 349: error trap added, since on iOS, getPropertyValue may fail // and crash in some cases, possibly if passed a text node try { if(e && (typeof(window.getComputedStyle) != 'undefined')) return window.getComputedStyle(e,'').getPropertyValue(s); else if(e && (typeof(e.currentStyle) != 'undefined')) //IE 8 and earlier return e.currentStyle[s]; } catch(ex){} // Return empty string if unable to get style value return ''; } /** * Get browser-independent computed style integer value for element (Build 349) * * @param {Element} e HTML element * @param {string} s CSS style name * @param {number=} d default value if NaN * @return {number} integer value of style */ util.getStyleInt = function(e,s,d) { var x=parseInt(util.getStyleValue(e,s),10); if(!isNaN(x)) return x; // Return the default value if numeric, else 0 if(typeof(d) == 'number') return d; else return 0; } /** * Set device parameters according to platform * TODO: put into separate function (?) */ var device=util.device={ touchable:!!('ontouchstart' in window),OS:'', formFactor:'desktop', dyPortrait:0, dyLandscape:0, version:'0', orientation:window.orientation }; if(navigator && navigator.userAgent) { var agent=navigator.userAgent; if(agent.indexOf('iPad') >= 0) { device.OS='iOS';device.formFactor='tablet'; device.dyPortrait=device.dyLandscape=0; } if(agent.indexOf('iPhone') >= 0) { device.OS='iOS';device.formFactor='phone'; device.dyPortrait=device.dyLandscape=25; } if(agent.indexOf('Android') >= 0) { device.OS='Android';device.formFactor='phone'; // form factor may be redefined on initialization device.dyPortrait=75; device.dyLandscape=25; try { var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)"); device.version=agent.match(rx)[1]; } catch(ex){} } if(agent.indexOf('Windows NT') >= 0) { device.OS='Windows'; if(agent.indexOf('Touch') >= 0) device.formFactor='phone'; // will be redefined as tablet if resolution high enough // Windows Phone and Tablet PC if(typeof navigator.msMaxTouchPoints == 'number' && navigator.msMaxTouchPoints > 0) device.touchable=true; } } // var sxx=device.formFactor; // Check and possibly revise form factor according to actual screen size (adjusted for Galaxy S, maybe OK generally?) if(device.formFactor == 'tablet' && Math.min(screen.width,screen.height) < 400) device.formFactor='phone'; if(device.formFactor == 'phone' && Math.max(screen.width,screen.height) > 720) device.formFactor='tablet'; // alert(sxx+'->'+device.formFactor); // Check for phony iOS devices (Win32 test excludes Chrome touch emulation on Windows)! if(device.OS == 'iOS' && !('ongesturestart' in window) && navigator.platform != 'Win32') device.OS='Android'; // Determine application or browser device.browser='web'; if(util._GetIEVersion() < 999) device.browser='ie'; else { if(device.OS == 'iOS' || device.OS.toLowerCase() == 'macosx') device.browser='safari'; var bMatch=/Firefox|Chrome|OPR/; if(bMatch.test(navigator.userAgent)) { if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) device.browser='firefox'; else if(navigator.userAgent.indexOf('OPR') >= 0) device.browser='opera'; else if(navigator.userAgent.indexOf('Chrome') >= 0) device.browser='chrome'; } } /* DEBUG: Force touch device (Build 360) device.touchable = true; device.browser = 'safari'; device.formFactor = 'tablet'; device.OS = 'iOS'; END DEBUG */ /** * Expose the touchable state for UIs - will disable external UIs entirely **/ util['isTouchDevice'] = function() { return device.touchable; } /** * Get orientation of tablet or phone display * * @return {boolean} */ util.portraitView = function() // new for I3363 (Build 301) { return !util.landscapeView(); } /** * Get orientation of tablet or phone display * * @return {boolean} */ util.landscapeView = function() // new for I3363 (Build 301) { // Assume portrait mode if orientation undefined if(typeof(window.orientation) == 'undefined') return false; // Else landscape for +/-90, portrait for 0, +/-180 return (Math.abs(window.orientation/90) == 1); } /** * Get viewport scale factor for this document * * @return {number} */ util.getViewportScale = function() { // This can sometimes fail with some browsers if called before document defined, // so catch the exception try { // Get viewport width var viewportWidth = document.documentElement.clientWidth; // Return a default value if screen width is greater than the viewport width (not fullscreen). if(screen.width > viewportWidth) return 1; // Get the orientation corrected screen width var screenWidth = screen.width; if(util.landscapeView()) { // Take larger of the two dimensions if(screen.width < screen.height) screenWidth = screen.height; } else { // Take smaller of the two dimensions if(screen.width > screen.height) screenWidth = screen.height; } // Calculate viewport scale return Math.round(100*screenWidth / window.innerWidth)/100; } catch(ex) { return 1; } } /** * Return height of URL bar on mobile devices, if visible * TODO: This does not seem to be right, so is not currently used * * @return {number} */ util.barHeight = function() { var dy=0; if(device.formFactor == 'phone') { dy=screen.height/2-window.innerHeight-(util.landscapeView()?device.dyLandscape:device.dyPortrait); } return dy; } /** * Function _EncodeEntities * Scope Private * @param {string} P_txt string to be encoded * @return {string} encoded (html-safe) string * Description Encode angle brackets and ampersand in text string */ util._EncodeEntities = function(P_txt) { return P_txt.replace('&','&').replace('<','<').replace('>','>'); // I1452 part 2 } /** * Function createShim * Scope Public * @return {Object} IFRAME element * Description Create an IFRAME element to go between KMW and drop down (to fix IE6 bug) */ util['createShim'] = util.createShim = function() // I1476 - Handle SELECT overlapping BEGIN { var e = util._CreateElement('IFRAME'); e.src = ''; e.style.display = 'none'; e.style.position = 'absolute'; e.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; e.frameBorder = '0'; e.scrolling = 'no'; return e; } // I1476 - Handle SELECT overlapping BEGIN /** * Function showShim * Scope Public * @param {Object} Pvkbd Visual keyboard DIV element * @param {Object} Pframe IFRAME shim element * @param {Object} Phelp OSK Help DIV element * Description Display iFrame under OSK at its currently defined position, to allow OSK to overlap SELECT elements (IE6 fix) */ util['showShim'] = util.showShim = function(Pvkbd,Pframe,Phelp) { if(Pframe) try { Pframe.style.left = util._GetAbsoluteX(Pvkbd)+'px'; Pframe.style.top = util._GetAbsoluteY(Pvkbd)+'px'; if(Phelp) { Pframe.style.width = (util._GetAbsoluteX(Phelp)-util._GetAbsoluteX(Pvkbd)+Phelp.offsetWidth)+"px"; Pframe.style.height = (util._GetAbsoluteY(Phelp)-util._GetAbsoluteY(Pvkbd)+Phelp.offsetHeight)+"px"; } else { Pframe.style.width = Pvkbd.offsetWidth+"px"; Pframe.style.height = Pvkbd.offsetHeight+"px"; } Pframe.style.zindex = '9999'; Pframe.style.display = 'block'; } catch (Lerr) {} } /** * Function hideShim * Scope Public * @param {Object} Pframe IFRAME shim element * Description Hide iFrame shim containing OSK */ util['hideShim'] = function(Pframe) { try { if(Pframe) Pframe.style.display = 'none'; } catch (err) {} } // I1476 - Handle SELECT overlapping END /** * Function rgba * Scope Public * @param {Object} s element style object * @param {number} r red value, 0-255 * @param {number} g green value, 0-255 * @param {number} b blue value, 0-255 * @param {number} a opacity value, 0-1.0 * @return {string} background colour style string * Description Browser-independent alpha-channel management */ util['rgba'] = util.rgba = function(s,r,g,b,a) { var bgColor='transparent'; if(util._GetIEVersion() < 9) { var pcOpacity=Math.floor(100*a),rs=r.toString(16),gs=g.toString(16),bs=b.toString(16),hexColor; rs=('00'+rs).substr(-2); gs=('00'+gs).substr(-2); bs=('00'+bs).substr(-2); hexColor=pcOpacity+rs+gs+bs; s.filter='progid:DXImageTransform.Microsoft.gradient(startColorstr=#'+hexColor+',endColorstr=#'+hexColor+')'; s.zoom='1'; } else { try { bgColor='rgba('+r+','+g+','+b+','+a+')'; } catch(ex) { bgColor='rgb('+r+','+g+','+b+')'; } } return bgColor; } /** * Add a stylesheet to a page programmatically, for use by the OSK, the UI or the page creator * * @param {string} s style string * @return {Object} returns the object reference **/ util['addStyleSheet'] = util.addStyleSheet = function(s) { var _ElemStyle = document.createElement('STYLE'); _ElemStyle.type = 'text/css'; if(_ElemStyle.styleSheet) // IE only { _ElemStyle.styleSheet.cssText = s; } else // all other browsers { _ElemStyle.appendChild(document.createTextNode(s)); } var _ElemHead=document.getElementsByTagName('HEAD'); if(_ElemHead.length > 0) { _ElemHead[0].appendChild(_ElemStyle); } else { document.body.appendChild(_ElemStyle); // Won't work on Chrome, ah well } return _ElemStyle; } /** * Remove a stylesheet element * * @param {Object} s style sheet reference * @return {boolean} false if element is not a style sheet **/ util['removeStyleSheet'] = util.removeStyleSheet = function(s) { if(s == null || typeof(s) != 'object') return false; if(s.nodeName != 'STYLE') return false; if(typeof(s.parentNode) == 'undefined' || s.parentNode == null) return false; s.parentNode.removeChild(s); return true; } /** * Add a reference to an external stylesheet file * * @param {string} s path to stylesheet file */ util['linkStyleSheet']=util.linkStyleSheet = function(s) { var headElements=document.getElementsByTagName('head'); if(headElements.length > 0) { var linkElement=document.createElement('link'); linkElement.type='text/css'; linkElement.rel='stylesheet'; linkElement.href=s; headElements[0].appendChild(linkElement); } } /** * Add a stylesheet with a font-face CSS descriptor for the embedded font appropriate * for the browser being used * * @param {Object} fd keymanweb font descriptor **/ util.addFontFaceStyleSheet = function(fd) { // Test if a valid font descriptor if(typeof(fd) == 'undefined') return; if(typeof(fd['files']) == 'undefined') fd['files']=fd['source']; if(typeof(fd['files']) == 'undefined') return; var i,ttf='',woff='',eot='',svg='',fList=[]; // TODO: 22 Aug 2014: check that font path passed from cloud is actually used! // Do not add a new font-face style sheet if already added for this font for(i=0; i 0) ttf=fList[i]; if(fList[i].indexOf('.woff') > 0) woff=fList[i]; if(fList[i].indexOf('.eot') > 0) eot=fList[i]; if(fList[i].indexOf('.svg') > 0) svg=fList[i]; } // Font path qualified to support page-relative fonts (build 347) if(ttf != '' && (ttf.indexOf('/') < 0)) ttf=keymanweb.options['fonts']+ttf; if(woff != '' && (woff.indexOf('/') < 0)) woff=keymanweb.options['fonts']+woff; if(eot != '' && (eot.indexOf('/') < 0)) eot=keymanweb.options['fonts']+eot; if(svg != '' && (svg.indexOf('/') < 0)) svg=keymanweb.options['fonts']+svg; // Build the font-face definition according to the browser being used var s='@font-face {\nfont-family:' +fd['family']+';\nfont-style:normal;\nfont-weight:normal;\n'; // Detect if Internet Explorer and version if so var IE=util._GetIEVersion(); // Build the font source string according to the browser, // but return without adding the style sheet if the required font type is unavailable // Modern browsers: use WOFF, TTF and fallback finally to SVG. Don't provide EOT if(IE >= 9) { if(device.OS == 'iOS') { if(ttf != '') { // Modify the url if required to prevent caching ttf = util.unCached(ttf); s=s+'src:url(\''+ttf+'\') format(\'truetype\');'; } else return; } else { var s0 = []; if(device.OS == 'Android') { // Android 4.2 and 4.3 have bugs in their rendering for some scripts // with embedded ttf or woff. svg mostly works so is a better initial // choice on the Android browser. if(svg != '') s0.push("url('"+svg+"') format('svg')"); if(woff != '') s0.push("url('"+woff+"') format('woff')"); if(ttf != '') s0.push("url('"+ttf+"') format('truetype')"); } else { if(woff != '') s0.push("url('"+woff+"') format('woff')"); if(ttf != '') s0.push("url('"+ttf+"') format('truetype')"); if(svg != '') s0.push("url('"+svg+"') format('svg')"); } if(s0.length == 0) return; s += 'src:'+s0.join(',')+';'; } } // IE 6-8 else { if(eot != '') s=s+'src:url(\''+eot+'\');'; else return; } s=s+'\n}\n'; util.addStyleSheet(s); keymanweb.embeddedFonts.push(fd['family']); } /** * Allow forced reload if necessary (stub only here) * * @param {string} s unmodified URL * @return modified URL */ util.unCached = function(s) { // var t=(new Date().getTime()); // s = s + '?v=' + t; return s; } /** * Document cookie parsing for use by kernel, OSK, UI etc. * * @param {string=} cn cookie name (optional) * @return {Object} array of names and strings, or array of variables and values */ util['loadCookie'] = util.loadCookie = function(cn) { var v={}; if(arguments.length > 0) { var cx = util['loadCookie'](); for(var t in cx) { if(t == cn) { var d = unescape(cx[t]).split(';'); for(var i=0; i 1) v[xc[0]] = xc[1]; else v[xc[0]] = ''; } } } } else { if(typeof(document.cookie) != 'undefined' && document.cookie != '') { var c = document.cookie.split(/;\s*/); for(var i = 0; i < c.length; i++) { var d = c[i].split('='); if(d.length == 2) v[d[0]] = d[1]; } } } return v; } /** * Standard cookie saving for use by kernel, OSK, UI etc. * * @param {string} cn name of cookie * @param {Object} cv object with array of named arguments and values */ util['saveCookie'] = util.saveCookie = function(cn,cv) { var s=''; for(var v in cv) s = s + v+'='+cv[v]+";"; var d = new Date(new Date().valueOf() + 1000 * 60 * 60 * 24 * 30).toGMTString(); document.cookie = cn+'='+escape(s)+'; path=/; expires='+d;//Fri, 31 Dec 2099 23:59:59 GMT;'; } /** * Function toNumber * Scope Public * @param {string} s numeric string * @param {number} v default value * @return {number} * Description Return string converted to integer or default value */ util['toNumber'] = util.toNumber = function(s,dflt) { var x = parseInt(s,10); return isNaN(x) ? dflt : x; } /** * Function toNumber * Scope Public * @param {string} s numeric string * @param {number} v default value * @return {number} * Description Return string converted to real value or default value */ util['toFloat'] = util.toFloat = function(s,dflt) { var x = parseFloat(s); return isNaN(x) ? dflt : x; } /** * Function toNzString * Scope Public * @param {*} item variable to test * @param {?*=} dflt default value * @return {*} * Description Test if a variable is null, false, empty string, or undefined, and return as string */ util['toNzString'] = util.nzString = function(item,dflt) { var dfltValue = ''; if(arguments.length > 1) dfltValue = dflt; if(typeof(item) == 'undefined') return dfltValue; if(item == null) return dfltValue; if(item == 0 || item == '') return dfltValue; return ''+item; } /** * Function deepCopy * Scope Private * @param {Object} p object to copy * @param {Array=} c0 array member being copied * @return {Object} clone ('deep copy') of object * Description Makes an actual copy (not a reference) of an object, copying simple members, * arrays and member objects but not functions, so use with care! */ util.deepCopy = function(p,c0) { var c = c0 || {}; for (var i in p) { if(typeof p[i] === 'object') { c[i] = (p[i].constructor === Array )? [] : {}; util.deepCopy(p[i],c[i]); } else c[i] = p[i]; } return c; } /** * Extend Array function by adding indexOf array member if undefined (IE < IE9) */ if(! ('indexOf' in Array)) { Array.prototype.indexOf = function(obj, start) { for(var i=(start || 0); i 1) bg.dismiss=fn; else bg.dismiss=null; } /** * Prepare the background and keyboard loading wait message box * Should not be called before options are defined during initialization **/ util.prepareWait = function() { var bg=document.createElement('DIV'), lb=document.createElement('DIV'), lt=document.createElement('DIV'), gr=document.createElement('DIV'), bx=document.createElement('DIV'); bg.className='kmw-wait-background'; lb.className='kmw-wait-box'; bg.dismiss=null; lt.className='kmw-wait-text'; gr.className='kmw-wait-graphic'; bx.className='kmw-alert-close'; // Close alert if anywhere in box is touched, since close box is too small on mobiles lb.onmousedown=lb.onclick=function(e) { // Ignore if waiting, only handle for alert if(bx.style.display == 'block') { bg.style.display='none'; if(bg.dismiss)bg.dismiss(); } }; lb.addEventListener('touchstart', lb.onclick, false); bg.onmousedown=bg.onclick=function(e){e.preventDefault();e.stopPropagation();} bg.addEventListener('touchstart', bg.onclick, false); lb.appendChild(bx); lb.appendChild(lt); lb.appendChild(gr); bg.appendChild(lb); document.body.appendChild(bg); keymanweb.waiting=bg; } /** * Get path of keymanweb script, for relative references * * *** This is not currently used, but may possibly be needed if *** * *** script identification during loading proves unreliable. *** * * @param {string} sName filename prefix *@return {string} path to source, with trailing slash **/ util.myPath = function(sName) { var i,scripts=document.getElementsByTagName('script'),ss; for(i=0; i= 0) return ss.src.substr(0,ss.src.lastIndexOf('/')+1); } return ''; } // Prepend the appropriate protocol if not included in path util.prependProtocol = function(path) { var pattern = new RegExp('^https?:'); if(pattern.test(path)) return path; else if(path.substr(0,2) == '//') return keymanweb.protocol+path; else if(path.substr(0,1) == '/') return keymanweb.protocol+'/'+path; else return keymanweb.protocol+'//'+path; } /** * Return the appropriate test string for a given font * * TODO: Tidy up and remove arrays once 'sample' included in font metadata * * @param {Object} fd font meta-data object * @return {string} string to compare width * */ util.testString = function(fd) { var fontName=fd['family'], i,s='BESbswy'; if('sample' in fd && typeof(fd['sample']) == 'string') return s+fd['sample']; var f=['TamilWeb','TibetanWeb','LatinWeb','CherokeeWeb', 'EgyptianWeb','SinhalaWeb','KhmerWeb','ArabicWeb', 'BurmeseWeb','LaoWeb','OriyaWeb','GeezWeb'], t=['\u0BBE\u0BF5','\u0F7F\u0FD0','\u02B0\u02A4','\u13D0\u13C9', '\uA723\uF7D3','\u0DD8\u0DA3','\u17D6\u178E','\u0639\u06B3', '\u1038\u1024','\u0EC0\u0EDD','\u0B03\u0B06','\u1361\u132C']; for(i=0; i