/*** KeymanWeb 10.0 Copyright 2017 SIL International ***/ var keyman = window['keyman'] || {}; /** @define {number} build counter that gets set by the build environment */ keyman.__BUILD__ = 299; /** * Base code: Declare major component namespaces, instances, and utility functions */ // If a copy of the script is already loaded, detect this and prevent re-initialization / data reset. if(!window['keyman']['loaded']) { (function() { var keymanweb = window['keyman'] = { _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 _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 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 globalKeyboard: null, // Indicates the currently-active keyboard for controls without independent keyboard settings. globalLanguageCode: null, // Indicates the language code corresponding to `globalKeyboard`. isEmbedded: false, // Indicates if the KeymanWeb instance is embedded within a mobile app. // Blocks full page initialization when set to `true`. refocusTimer: 0, // Tracks a timeout event that aids of OSK modifier/state key tracking when the document loses focus. modStateFlags: 0 // Tracks the present state of the physical keyboard's active modifier flags. Needed for AltGr simulation. }; // Signals that a KMW load has occurred in order to prevent double-loading. keymanweb['loaded'] = true; keymanweb['initialized'] = 0; keymanweb['build'] = keyman.__BUILD__; keymanweb['version'] = '10.0'; keymanweb['helpURL'] = 'http://help.keyman.com/go'; // Define public OSK, user interface and utility function objects var util = keymanweb['util'] = {}; var osk = keymanweb['osk'] = {ready:false}; var ui = keymanweb['ui'] = {}; var kbdInterface = keymanweb['interface'] = { // Cross-reference with /windows/src/global/inc/Compiler.h - these are the Developer codes for the respective system stores. // They're named here identically to their use in that header file. TSS_LAYER: 33, TSS_PLATFORM: 31, _AnyIndices: [], // AnyIndex - array of any/index match indices _BeepObjects: [], // BeepObjects - maintains a list of active 'beep' visual feedback elements _BeepTimeout: 0, // BeepTimeout - a flag indicating if there is an active 'beep'. // Set to 1 if there is an active 'beep', otherwise leave as '0'. _DeadKeys: [] // DeadKeys - array of matched deadkeys }; // Stub functions (defined later in code only if required) keymanweb.setDefaultDeviceOptions = function(opt){} keymanweb.getStyleSheetPath = function(s){return s;} keymanweb.getKeyboardPath=function(f, p){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 = {}; // An object mapping event names to individual event lists. Maps strings to arrays. util.currentEvents = []; // The event messaging call stack. /** * 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 arrayFromNodeList * Scope Public * @param {Object} nl a node list, as returned from getElementsBy_____ methods. * Description Transforms a node list into an array. * * @return {Array} */ util.arrayFromNodeList = function(nl) { var res = []; for(var i=0; i < nl.length; i++) { res.push(nl[i]); } return res; } /** * 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} optionName 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 */ /* If we've made it to this point of initialization and aren't anything else, KeymanWeb assumes * we're a desktop. Since we don't yet support desktops with touch-based input, we disable it here. */ if(device.formFactor == 'desktop') { device.touchable = false; } /** * Represents hardware-based keystrokes regardless of the 'true' device, facilitating hardware keyboard input * whenever touch-based input is available. */ util.physicalDevice = { touchable: false, browser: device.browser, formFactor: 'desktop', OS: device.OS }; /** * 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 * Description [Deprecated] 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 { console.warn("The util.createShim function is deprecated, as its old functionality is no longer needed. " + "It and references to its previously-produced shims may be safely removed."); return; } // 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 [Deprecated] 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) { console.warn("The util.showShim function is deprecated, as its old functionality is no longer needed. It may be safely removed."); } /** * Function hideShim * Scope Public * @param {Object} Pframe IFRAME shim element * Description [Deprecated] Hide iFrame shim containing OSK */ util['hideShim'] = function(Pframe) { console.warn("The util.hideShim function is deprecated, as its old functionality is no longer needed. It may be safely removed."); } /** * 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} dflt 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} dflt 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