From e955c16777ebe78b089cc920ef80b8139373449b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 14 Dec 2018 10:02:50 +0700 Subject: [PATCH 01/15] Start of development on a 'bulk rendering' engine for KMW validation. --- web/bulk_rendering/build.sh | 54 ++++++++++++++++++++ web/bulk_rendering/index.html | 76 +++++++++++++++++++++++++++++ web/bulk_rendering/renderer_core.ts | 47 ++++++++++++++++++ web/bulk_rendering/tsconfig.json | 13 +++++ web/bulk_rendering/us-1.0.js | 1 + web/source/kmwkeyboards.ts | 2 +- 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 web/bulk_rendering/build.sh create mode 100644 web/bulk_rendering/index.html create mode 100644 web/bulk_rendering/renderer_core.ts create mode 100644 web/bulk_rendering/tsconfig.json create mode 100644 web/bulk_rendering/us-1.0.js diff --git a/web/bulk_rendering/build.sh b/web/bulk_rendering/build.sh new file mode 100644 index 0000000000..936b0d27c1 --- /dev/null +++ b/web/bulk_rendering/build.sh @@ -0,0 +1,54 @@ +#! /bin/bash +# +# Compile the KeymanWeb Recorder module for use with developing/running engine tests. +# + +# Fails the build if a specified file does not exist. +assert ( ) { + if ! [ -f $1 ]; then + fail "Build failed." + exit 1 + fi +} + +fail() { + FAILURE_MSG="$1" + if [[ "$FAILURE_MSG" == "" ]]; then + FAILURE_MSG="Unknown failure" + fi + echo "${ERROR_RED}$FAILURE_MSG${NORMAL}" + exit 1 +} + +# Ensure the dependencies are downloaded. --no-optional should help block fsevents warnings. +echo "Node.js + dependencies check" +npm install --no-optional + +if [ $? -ne 0 ]; then + fail "Build environment setup error detected! Please ensure Node.js is installed!" +fi + +# Definition of global compile constants +COMPILED_FILE="bulk_render.js" +OUTPUT="../release/renderer" +NODE_SOURCE="bulk_rendering" +#ENGINE_TEST_OUTPUT="../unit_tests/" + +readonly OUTPUT +readonly NODE_SOURCE +#readonly ENGINE_TEST_OUTPUT + +# Ensures that we rely first upon the local npm-based install of Typescript. +# (Facilitates automated setup for build agents.) +PATH="../../node_modules/.bin:$PATH" + +compiler="npm run tsc --" +compilecmd="$compiler" + +$compilecmd -p $NODE_SOURCE/tsconfig.json +if [ $? -ne 0 ]; then + fail "Typescript compilation failed." +fi + +#cp $OUTPUT/$COMPILED_FILE $ENGINE_TEST_OUTPUT +#cp $OUTPUT/$COMPILED_FILE.map $ENGINE_TEST_OUTPUT \ No newline at end of file diff --git a/web/bulk_rendering/index.html b/web/bulk_rendering/index.html new file mode 100644 index 0000000000..819263f9af --- /dev/null +++ b/web/bulk_rendering/index.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + KeymanWeb Sample Page - Unminified Source + + + + + + + + + + + + + + +

KeymanWeb Bulk Rendering

+ + + + + + diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts new file mode 100644 index 0000000000..3effca7423 --- /dev/null +++ b/web/bulk_rendering/renderer_core.ts @@ -0,0 +1,47 @@ +// Includes KeymanWeb's Device class, as it's quite a useful resource for KMW-related projects. +/// + +type KeyboardMap = {[id: string]: any}; + +namespace com.keyman.renderer { + + + export class BatchRenderer { + // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. + private filterKeyboards(): KeyboardMap { + let keyman = window['keyman']; + + let kbds = keyman['getKeyboards'](); + + let keyboardMap = []; + + for(var i = 0; i < kbds.length; i++) { + let id: string = kbds[i]["InternalName"]; + if(keyboardMap[id]) { + continue; + } else { + keyboardMap[id] = kbds[i]; + } + } + + return keyboardMap; + } + + run() { + if(window['keyman']) { + let keyman = window['keyman']; + + // Assumes that the keyboards have been preloaded for us. + let kbds = this.filterKeyboards(); + + console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); + } else { + console.error("KeymanWeb not detected!"); + } + } + } + + (function(){ + window['kmw_renderer'] = new com.keyman.renderer.BatchRenderer(); + })(); +} \ No newline at end of file diff --git a/web/bulk_rendering/tsconfig.json b/web/bulk_rendering/tsconfig.json new file mode 100644 index 0000000000..8c09d55693 --- /dev/null +++ b/web/bulk_rendering/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "inlineSources": true, + "module": "none", + "outFile": "../release/renderer/bulk_render.js", + "sourceMap": true, + "target": "es5" + }, + "files": [ + "renderer_core.ts" + ] +} diff --git a/web/bulk_rendering/us-1.0.js b/web/bulk_rendering/us-1.0.js new file mode 100644 index 0000000000..b78e19f4e5 --- /dev/null +++ b/web/bulk_rendering/us-1.0.js @@ -0,0 +1 @@ +KeymanWeb.KR(new Keyboard_us()); function Keyboard_us() {this.KI="Keyboard_us";this.KN="US";this.KV={F:' 1em "Arial"',K102:0,BK:new Array("`","1","2","3","4","5","6","7","8","9","0","-","=","","","","q","w","e","r","t","y","u","i","o","p","[","]","\\","","","","a","s","d","f","g","h","j","k","l",";","'","","","","","","\\","z","x","c","v","b","n","m",",",".","/","","","","",""," ","~","!","@","#","$","%","^","&","*","(",")","_","+","","","","Q","W","E","R","T","Y","U","I","O","P","{","}","|","","","","A","S","D","F","G","H","J","K","L",":","\"","","","","","","|","Z","X","C","V","B","N","M","<",">","?","","","","",""," ","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""," ")};this.KH='';this.KM=0;this.gs=function(t,e){return this.g0(t,e);};this.g0=function(t,e){var k=KeymanWeb,m=0;if(k.KKM(e,16384,32)){m=1;k.KO(0,t," ");}else if(k.KKM(e,16400,32)){m=1;k.KO(0,t," ");}else if(k.KKM(e,16416,32)){m=1;k.KO(0,t," ");}else if(k.KKM(e,16384,48)){m=1;k.KO(0,t,"0");}else if(k.KKM(e,16400,48)){m=1;k.KO(0,t,")");}else if(k.KKM(e,16384,49)){m=1;k.KO(0,t,"1");}else if(k.KKM(e,16400,49)){m=1;k.KO(0,t,"!");}else if(k.KKM(e,16384,50)){m=1;k.KO(0,t,"2");}else if(k.KKM(e,16400,50)){m=1;k.KO(0,t,"@");}else if(k.KKM(e,16384,51)){m=1;k.KO(0,t,"3");}else if(k.KKM(e,16400,51)){m=1;k.KO(0,t,"#");}else if(k.KKM(e,16384,52)){m=1;k.KO(0,t,"4");}else if(k.KKM(e,16400,52)){m=1;k.KO(0,t,"$");}else if(k.KKM(e,16384,53)){m=1;k.KO(0,t,"5");}else if(k.KKM(e,16400,53)){m=1;k.KO(0,t,"%");}else if(k.KKM(e,16384,54)){m=1;k.KO(0,t,"6");}else if(k.KKM(e,16400,54)){m=1;k.KO(0,t,"^");}else if(k.KKM(e,16384,55)){m=1;k.KO(0,t,"7");}else if(k.KKM(e,16400,55)){m=1;k.KO(0,t,"&");}else if(k.KKM(e,16384,56)){m=1;k.KO(0,t,"8");}else if(k.KKM(e,16400,56)){m=1;k.KO(0,t,"*");}else if(k.KKM(e,16384,57)){m=1;k.KO(0,t,"9");}else if(k.KKM(e,16400,57)){m=1;k.KO(0,t,"(");}else if(k.KKM(e,16384,65)){m=1;k.KO(0,t,"a");}else if(k.KKM(e,16400,65)){m=1;k.KO(0,t,"A");}else if(k.KKM(e,16384,66)){m=1;k.KO(0,t,"b");}else if(k.KKM(e,16400,66)){m=1;k.KO(0,t,"B");}else if(k.KKM(e,16384,67)){m=1;k.KO(0,t,"c");}else if(k.KKM(e,16400,67)){m=1;k.KO(0,t,"C");}else if(k.KKM(e,16384,68)){m=1;k.KO(0,t,"d");}else if(k.KKM(e,16400,68)){m=1;k.KO(0,t,"D");}else if(k.KKM(e,16384,69)){m=1;k.KO(0,t,"e");}else if(k.KKM(e,16400,69)){m=1;k.KO(0,t,"E");}else if(k.KKM(e,16384,70)){m=1;k.KO(0,t,"f");}else if(k.KKM(e,16400,70)){m=1;k.KO(0,t,"F");}else if(k.KKM(e,16384,71)){m=1;k.KO(0,t,"g");}else if(k.KKM(e,16400,71)){m=1;k.KO(0,t,"G");}else if(k.KKM(e,16384,72)){m=1;k.KO(0,t,"h");}else if(k.KKM(e,16400,72)){m=1;k.KO(0,t,"H");}else if(k.KKM(e,16384,73)){m=1;k.KO(0,t,"i");}else if(k.KKM(e,16400,73)){m=1;k.KO(0,t,"I");}else if(k.KKM(e,16384,74)){m=1;k.KO(0,t,"j");}else if(k.KKM(e,16400,74)){m=1;k.KO(0,t,"J");}else if(k.KKM(e,16384,75)){m=1;k.KO(0,t,"k");}else if(k.KKM(e,16400,75)){m=1;k.KO(0,t,"K");}else if(k.KKM(e,16384,76)){m=1;k.KO(0,t,"l");}else if(k.KKM(e,16400,76)){m=1;k.KO(0,t,"L");}else if(k.KKM(e,16384,77)){m=1;k.KO(0,t,"m");}else if(k.KKM(e,16400,77)){m=1;k.KO(0,t,"M");}else if(k.KKM(e,16384,78)){m=1;k.KO(0,t,"n");}else if(k.KKM(e,16400,78)){m=1;k.KO(0,t,"N");}else if(k.KKM(e,16384,79)){m=1;k.KO(0,t,"o");}else if(k.KKM(e,16400,79)){m=1;k.KO(0,t,"O");}else if(k.KKM(e,16384,80)){m=1;k.KO(0,t,"p");}else if(k.KKM(e,16400,80)){m=1;k.KO(0,t,"P");}else if(k.KKM(e,16384,81)){m=1;k.KO(0,t,"q");}else if(k.KKM(e,16400,81)){m=1;k.KO(0,t,"Q");}else if(k.KKM(e,16384,82)){m=1;k.KO(0,t,"r");}else if(k.KKM(e,16400,82)){m=1;k.KO(0,t,"R");}else if(k.KKM(e,16384,83)){m=1;k.KO(0,t,"s");}else if(k.KKM(e,16400,83)){m=1;k.KO(0,t,"S");}else if(k.KKM(e,16384,84)){m=1;k.KO(0,t,"t");}else if(k.KKM(e,16400,84)){m=1;k.KO(0,t,"T");}else if(k.KKM(e,16384,85)){m=1;k.KO(0,t,"u");}else if(k.KKM(e,16400,85)){m=1;k.KO(0,t,"U");}else if(k.KKM(e,16384,86)){m=1;k.KO(0,t,"v");}else if(k.KKM(e,16400,86)){m=1;k.KO(0,t,"V");}else if(k.KKM(e,16384,87)){m=1;k.KO(0,t,"w");}else if(k.KKM(e,16400,87)){m=1;k.KO(0,t,"W");}else if(k.KKM(e,16384,88)){m=1;k.KO(0,t,"x");}else if(k.KKM(e,16400,88)){m=1;k.KO(0,t,"X");}else if(k.KKM(e,16384,89)){m=1;k.KO(0,t,"y");}else if(k.KKM(e,16400,89)){m=1;k.KO(0,t,"Y");}else if(k.KKM(e,16384,90)){m=1;k.KO(0,t,"z");}else if(k.KKM(e,16400,90)){m=1;k.KO(0,t,"Z");}else if(k.KKM(e,16384,186)){m=1;k.KO(0,t,";");}else if(k.KKM(e,16400,186)){m=1;k.KO(0,t,":");}else if(k.KKM(e,16384,187)){m=1;k.KO(0,t,"=");}else if(k.KKM(e,16400,187)){m=1;k.KO(0,t,"+");}else if(k.KKM(e,16384,188)){m=1;k.KO(0,t,",");}else if(k.KKM(e,16400,188)){m=1;k.KO(0,t,"<");}else if(k.KKM(e,16384,189)){m=1;k.KO(0,t,"-");}else if(k.KKM(e,16400,189)){m=1;k.KO(0,t,"_");}else if(k.KKM(e,16384,190)){m=1;k.KO(0,t,".");}else if(k.KKM(e,16400,190)){m=1;k.KO(0,t,">");}else if(k.KKM(e,16384,191)){m=1;k.KO(0,t,"/");}else if(k.KKM(e,16400,191)){m=1;k.KO(0,t,"?");}else if(k.KKM(e,16384,192)){m=1;k.KO(0,t,"`");}else if(k.KKM(e,16400,192)){m=1;k.KO(0,t,"~");}else if(k.KKM(e,16384,219)){m=1;k.KO(0,t,"[");}else if(k.KKM(e,16400,219)){m=1;k.KO(0,t,"{");}else if(k.KKM(e,16384,220)){m=1;k.KO(0,t,"\\");}else if(k.KKM(e,16400,220)){m=1;k.KO(0,t,"|");}else if(k.KKM(e,16384,221)){m=1;k.KO(0,t,"]");}else if(k.KKM(e,16400,221)){m=1;k.KO(0,t,"}");}else if(k.KKM(e,16384,222)){m=1;k.KO(0,t,"'");}else if(k.KKM(e,16400,222)){m=1;k.KO(0,t,"\"");}else if(k.KKM(e,16384,226)){m=1;k.KO(0,t,"\\");}else if(k.KKM(e,16400,226)){m=1;k.KO(0,t,"|");}return m;};} \ No newline at end of file diff --git a/web/source/kmwkeyboards.ts b/web/source/kmwkeyboards.ts index b376a3e0b9..870fefd04f 100644 --- a/web/source/kmwkeyboards.ts +++ b/web/source/kmwkeyboards.ts @@ -305,7 +305,7 @@ namespace com.keyman { // Fixed OSK font issue Github #7 (9/1/2015) if(typeof(lp['oskFont']) != 'undefined') { - sp['KOskFont'] = (typeof sp['KOskFont'] === 'undefined') ? new KeyboardFont(lp['oskfont'], fontPath) : sp['KOskFont']; + sp['KOskFont'] = (typeof sp['KOskFont'] === 'undefined') ? new KeyboardFont(lp['oskFont'], fontPath) : sp['KOskFont']; } // Update the UI From e8cc2a7c7b4c0ef7114556317f4b08dd2e240d44 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 14 Dec 2018 14:14:02 +0700 Subject: [PATCH 02/15] Prototyping's going well; even IE renders here. One kbd, one layer. --- web/bulk_rendering/renderer_core.ts | 50 +++++++++++++++++++++++++++-- web/package-lock.json | 24 ++++++++++++++ web/package.json | 2 ++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index 3effca7423..109f0a52f9 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -1,11 +1,13 @@ // Includes KeymanWeb's Device class, as it's quite a useful resource for KMW-related projects. /// +// Needed for OSK rendering to image files. +/// +// Ensure that Promises are within scope. +/// type KeyboardMap = {[id: string]: any}; namespace com.keyman.renderer { - - export class BatchRenderer { // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. private filterKeyboards(): KeyboardMap { @@ -16,7 +18,7 @@ namespace com.keyman.renderer { let keyboardMap = []; for(var i = 0; i < kbds.length; i++) { - let id: string = kbds[i]["InternalName"]; + let id: string = kbds[i]['InternalName']; if(keyboardMap[id]) { continue; } else { @@ -27,6 +29,45 @@ namespace com.keyman.renderer { return keyboardMap; } + private render(ele: HTMLElement, isMobile?: boolean) { + let html2canvas = window['html2canvas']; + + let imgOut = document.createElement('img'); + document.body.appendChild(imgOut); + + // Warning - needs Promises, so it'll need a polyfill for IE. + let canvasParams = { + 'scale': 1, + 'width': window.innerWidth // Good for mobile, less-so for desktop. + } + + if(!isMobile) { + canvasParams['width'] = 500; + ele.style.width = '500px'; + } + + html2canvas(ele, canvasParams).then(function(canvas) { + imgOut.src = canvas.toDataURL(); + }); + } + + private process(kbd) { + let keyman = window['keyman']; + + keyman.setActiveKeyboard(kbd['InternalName']); + + // Really could use promises to tie these two together... + setTimeout(function() { + let box: HTMLDivElement = keyman.osk._Box; + keyman.osk.show(true); + + // Forcing display, width, and height here helps to ensure a nice, consistent image. + box.style.display = 'block'; + + this.render(box, keyman.util.device.formFactor != 'desktop'); + }.bind(this), 2500); + } + run() { if(window['keyman']) { let keyman = window['keyman']; @@ -35,6 +76,9 @@ namespace com.keyman.renderer { let kbds = this.filterKeyboards(); console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); + + // Temporary - just load the first keyboard. + this.process(kbds[Object.keys(kbds)[0]]); } else { console.error("KeymanWeb not detected!"); } diff --git a/web/package-lock.json b/web/package-lock.json index dfa4a15c27..64b4353c96 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -701,6 +701,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "css-line-break": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.0.1.tgz", + "integrity": "sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo=", + "dev": true, + "requires": { + "base64-arraybuffer": "^0.1.5" + } + }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -1975,6 +1984,15 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "html2canvas": { + "version": "1.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz", + "integrity": "sha1-OxmS48mz9WBjw1/WIElPN+uohRM=", + "dev": true, + "requires": { + "css-line-break": "1.0.1" + } + }, "http-errors": { "version": "1.6.3", "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -3034,6 +3052,12 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, + "promise-polyfill": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", + "dev": true + }, "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", diff --git a/web/package.json b/web/package.json index 28d9caca54..793d0b0a24 100644 --- a/web/package.json +++ b/web/package.json @@ -21,6 +21,7 @@ "chai": "^4.2.0", "google-closure-compiler": "^20171203.0.0", "google-closure-library": "^20171203.0.0", + "html2canvas": "^1.0.0-alpha.12", "karma": "^3.1.1", "karma-browserstack-launcher": "^1.3.0", "karma-chai": "^0.1.0", @@ -37,6 +38,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^5.2.0", "modernizr": "^3.6.0", + "promise-polyfill": "^8.1.0", "typescript": "^3.1.6" }, "scripts": { From c95ba33739360628e03ef046f7642ace1939beee Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 14 Dec 2018 15:16:33 +0700 Subject: [PATCH 03/15] Establishes proper Promise fulfillment for one full keyboard. --- web/bulk_rendering/renderer_core.ts | 51 +++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index 109f0a52f9..af94ea4847 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -29,7 +29,7 @@ namespace com.keyman.renderer { return keyboardMap; } - private render(ele: HTMLElement, isMobile?: boolean) { + private render(resolve, ele: HTMLElement, layerIndex: number, isMobile?: boolean) { let html2canvas = window['html2canvas']; let imgOut = document.createElement('img'); @@ -48,26 +48,65 @@ namespace com.keyman.renderer { html2canvas(ele, canvasParams).then(function(canvas) { imgOut.src = canvas.toDataURL(); + resolve(layerIndex); }); } - private process(kbd) { + private processKeyboard(kbd) { let keyman = window['keyman']; - keyman.setActiveKeyboard(kbd['InternalName']); + let isMobile = keyman.util.device.formFactor != 'desktop'; // Really could use promises to tie these two together... setTimeout(function() { let box: HTMLDivElement = keyman.osk._Box; keyman.osk.show(true); - + // Forcing display, width, and height here helps to ensure a nice, consistent image. box.style.display = 'block'; + + let layers = keyman.osk._DivVKbd.firstChild.childNodes; - this.render(box, keyman.util.device.formFactor != 'desktop'); + let renderLayer = function(i) { + return new Promise(function(resolve) { + for(var j = 0; j < layers.length; j++) { + layers[j].style.display = 'none'; + } + + layers[i].style.display = 'block'; + + this.render(resolve, box, i, isMobile); + }.bind(this)) + }.bind(this); + + return this.arrayPromiseIteration(renderLayer, layers.length).then(function() { + console.log("All renders for the first keyboard should now be complete."); + }); }.bind(this), 2500); } + // Synchronously performs asynchronous operations across a loop, one at a time. + // Necessary due to the nature of KMW OSK rendering. + private arrayPromiseIteration(promiseGenerator: (i: number) => Promise, length: number): Promise { + let iteration = function(index: number): Promise { + if(index < length) { + var promise = promiseGenerator(index); + return promise.then(function(index: number) { + return iteration(++index); + }) + } else { + return Promise.resolve(); + } + // return promise.then(function(index: number) { + // if(++index < length) { + // return iteration(index); + // } // else instantly return. + // }); + } + + return iteration(0); + } + run() { if(window['keyman']) { let keyman = window['keyman']; @@ -78,7 +117,7 @@ namespace com.keyman.renderer { console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); // Temporary - just load the first keyboard. - this.process(kbds[Object.keys(kbds)[0]]); + this.processKeyboard(kbds[Object.keys(kbds)[0]]); } else { console.error("KeymanWeb not detected!"); } From 723c24130fa8382a6b818f46d6c6bfa9f8afd0ce Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 14 Dec 2018 15:30:59 +0700 Subject: [PATCH 04/15] Sets the promise-polyfill library as a true dependency. --- web/package-lock.json | 3 +-- web/package.json | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 64b4353c96..90decfd74a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -3055,8 +3055,7 @@ "promise-polyfill": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==" }, "qjobs": { "version": "1.2.0", diff --git a/web/package.json b/web/package.json index 793d0b0a24..a0389901f0 100644 --- a/web/package.json +++ b/web/package.json @@ -38,7 +38,6 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^5.2.0", "modernizr": "^3.6.0", - "promise-polyfill": "^8.1.0", "typescript": "^3.1.6" }, "scripts": { @@ -46,5 +45,8 @@ "test": "bash ./unit_tests/test.sh", "karma": "karma", "modernizr": "modernizr" + }, + "dependencies": { + "promise-polyfill": "^8.1.0" } } From 9561537101f69a4e60e60e02344d3048133f68e8 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 17 Dec 2018 09:09:53 +0700 Subject: [PATCH 05/15] setActiveKeyboard is now Promise-compatible! --- web/bulk_rendering/index.html | 2 +- web/bulk_rendering/renderer_core.ts | 34 +++++++++++-------- web/source/kmwbase.ts | 6 ++-- web/source/kmwkeyboards.ts | 52 +++++++++++++++++++---------- 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/web/bulk_rendering/index.html b/web/bulk_rendering/index.html index 819263f9af..6fe69e9918 100644 --- a/web/bulk_rendering/index.html +++ b/web/bulk_rendering/index.html @@ -16,7 +16,7 @@ @@ -50,9 +48,11 @@ - -

KeymanWeb Bulk Rendering

- + +

KeymanWeb - Bulk On-Screen Keyboard Rendering

+ +
+
diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index f4c2173325..fc9128ef15 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -9,6 +9,8 @@ type KeyboardMap = {[id: string]: any}; namespace com.keyman.renderer { export class BatchRenderer { + static divMaster: HTMLDivElement; + // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. private filterKeyboards(): KeyboardMap { let keyman = window['keyman']; @@ -29,29 +31,26 @@ namespace com.keyman.renderer { return keyboardMap; } - private render(resolve, ele: HTMLElement, layerIndex: number, isMobile?: boolean) { + private render(ele: HTMLElement, isMobile?: boolean): Promise { let html2canvas = window['html2canvas']; let imgOut = document.createElement('img'); - let br = document.createElement('br'); - document.body.appendChild(imgOut); - document.body.appendChild(br); - // Warning - needs Promises, so it'll need a polyfill for IE. let canvasParams = { 'logging': false, 'scale': 1, 'width': window.innerWidth // Good for mobile, less-so for desktop. } + // So, if it's desktop, we set more reasonable values. if(!isMobile) { canvasParams['width'] = 500; ele.style.width = '500px'; } - html2canvas(ele, canvasParams).then(function(canvas) { + return html2canvas(ele, canvasParams).then(function(canvas) { imgOut.src = canvas.toDataURL(); - resolve(layerIndex); + return imgOut; }); } @@ -62,21 +61,29 @@ namespace com.keyman.renderer { // A nice, closure-friendly reference for use in our callbacks. let renderer = this; - // Really could use promises to tie these two together... + // Once the keyboard's loaded, we can really get started. return p.then(function() { let box: HTMLDivElement = keyman.osk._Box; // Uses 'private' APIs that may be subject to change in the future. Keep it updated! let layers = keyman.keyboardManager.activeKeyboard.KV.KLS; - let renderLayer = function(i) { + let renderLayer = function(i: number) { return new Promise(function(resolve) { // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. keyman.osk.layerId = Object.keys(layers)[i]; keyman.osk.show(true); - renderer.render(resolve, box, i, isMobile); + renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { + let br = document.createElement('br'); + // BatchRenderer.divMaster.appendChild(imgOut); + // BatchRenderer.divMaster.appendChild(br); + + BatchRenderer.divMaster.insertAdjacentElement('afterbegin', br); + BatchRenderer.divMaster.insertAdjacentElement('afterbegin', imgEle); + resolve(i); + }); }) }; @@ -113,6 +120,8 @@ namespace com.keyman.renderer { let dummy = document.createElement('imput'); com.keyman['DOMEventHandlers'].states.activeElement = dummy; + BatchRenderer.divMaster = document.getElementById('renderList'); + // We want the renderer to control where the keyboard is displayed. // Also bypasses another 'fun' OSK complication. keyman.osk.userPositioned = true; From 8d581c7436cd5d73d8cccdc32b45a512f1cc964a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 17 Dec 2018 14:06:19 +0700 Subject: [PATCH 11/15] Fixes some mobile issues, polishes the basic page structure. --- web/bulk_rendering/index.html | 7 +- web/bulk_rendering/renderer_core.ts | 107 +++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 15 deletions(-) diff --git a/web/bulk_rendering/index.html b/web/bulk_rendering/index.html index 509ad79170..437ebac78d 100644 --- a/web/bulk_rendering/index.html +++ b/web/bulk_rendering/index.html @@ -49,10 +49,9 @@ -

KeymanWeb - Bulk On-Screen Keyboard Rendering

- -
-
+

KeymanWeb - Bulk On-Screen Keyboard Rendering

+
+
diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index fc9128ef15..371fc04550 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -10,6 +10,7 @@ type KeyboardMap = {[id: string]: any}; namespace com.keyman.renderer { export class BatchRenderer { static divMaster: HTMLDivElement; + static dummy: HTMLInputElement; // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. private filterKeyboards(): KeyboardMap { @@ -54,43 +55,98 @@ namespace com.keyman.renderer { }); } + createKeyboardHeader(kbd, loaded: boolean): HTMLDivElement { + let divHeader = document.createElement('div'); + let eleName = document.createElement('h2'); + + eleName.textContent = 'ID: ' + kbd['InternalName']; + divHeader.appendChild(eleName); + + let eleDescription = document.createElement('p'); + + if(loaded) { + + eleDescription.appendChild(document.createTextNode('Name: ' + kbd['Name'])); + eleDescription.appendChild(document.createElement('br')); + eleDescription.appendChild(document.createTextNode('Font: ' + window['keyman'].keyboardManager.activeKeyboard.KV.F)); + + } else { + eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); + } + + divHeader.appendChild(eleDescription); + + return divHeader; + } + private processKeyboard(kbd) { let keyman = window['keyman']; let p: Promise = keyman.setActiveKeyboard(kbd['InternalName']); let isMobile = keyman.util.device.formFactor != 'desktop'; + // Establish common keyboard header info. + let divSummary = document.createElement('div'); + // Establishes a linkable target for this keyboard's data. + divSummary.id = "summary-" + kbd['InternalName']; + + BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); + // A nice, closure-friendly reference for use in our callbacks. let renderer = this; // Once the keyboard's loaded, we can really get started. return p.then(function() { let box: HTMLDivElement = keyman.osk._Box; + + divSummary.appendChild(renderer.createKeyboardHeader(kbd, true)); + + let divRenders = document.createElement('div'); + divSummary.appendChild(divRenders); // Uses 'private' APIs that may be subject to change in the future. Keep it updated! - let layers = keyman.keyboardManager.activeKeyboard.KV.KLS; + var layers; + if(isMobile) { + layers = keyman.osk.layers; + } else { + // The desktop OSK will be overpopulated, with a number of blank layers to display in most cases. + // We instead rely upon the KLS definition to ensure we keep the renders sparse. + layers = keyman.keyboardManager.activeKeyboard.KV.KLS; + } let renderLayer = function(i: number) { return new Promise(function(resolve) { // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. - keyman.osk.layerId = Object.keys(layers)[i]; + if(isMobile) { + keyman.osk.layerId = layers[i].id; + } else { + keyman.osk.layerId = Object.keys(layers)[i]; + } + // Make sure the active element's still set! + renderer.setActiveDummy(); keyman.osk.show(true); + // Juuuust in case it's not otherwise showing. (Sometimes happens after scrolling on touch-devices.) + box.style.display = 'block'; renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { - let br = document.createElement('br'); - // BatchRenderer.divMaster.appendChild(imgOut); - // BatchRenderer.divMaster.appendChild(br); + let eleLayer = document.createElement('div'); + let eleLayerId = document.createElement('p'); + eleLayerId.textContent = 'Layer ID: ' + (isMobile ? keyman.osk.layers[i].id : Object.keys(layers)[i]); - BatchRenderer.divMaster.insertAdjacentElement('afterbegin', br); - BatchRenderer.divMaster.insertAdjacentElement('afterbegin', imgEle); + eleLayer.appendChild(eleLayerId); + eleLayer.appendChild(imgEle); + eleLayer.appendChild(document.createElement('br')); + + divRenders.appendChild(eleLayer); resolve(i); }); }) }; // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. - return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); + return renderer.arrayPromiseIteration(renderLayer, isMobile ? keyman.osk.layers.length : Object.keys(layers).length); }).catch(function() { console.log("Failed to load the \"" + kbd['InternalName'] + "\" keyboard for rendering!"); + divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); return Promise.resolve(); }); } @@ -112,15 +168,39 @@ namespace com.keyman.renderer { return iteration(0); } + fillDeviceNotes() { + let description = document.createElement('p'); + let device = new com.keyman.Device(); + device.detect(); + + description.appendChild(document.createTextNode('Browser: ' + device.browser)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('OS: ' + device.OS)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Form factor: ' + device.formFactor)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Touchable: ' + device.touchable)); + + document.getElementById('deviceNotes').appendChild(description); + } + + setActiveDummy() { + com.keyman['DOMEventHandlers'].states.activeElement = BatchRenderer.dummy; + } + run() { if(window['keyman']) { let keyman = window['keyman']; // Establish a 'dummy' element to bypass the 'nothing's active' check KMW usualy uses. - let dummy = document.createElement('imput'); - com.keyman['DOMEventHandlers'].states.activeElement = dummy; + BatchRenderer.dummy = document.createElement('input'); + this.setActiveDummy(); BatchRenderer.divMaster = document.getElementById('renderList'); + if(BatchRenderer.divMaster.childElementCount > 0) { + console.log("Prior bulk-renderer run detected. Terminating execution."); + return; + } // We want the renderer to control where the keyboard is displayed. // Also bypasses another 'fun' OSK complication. @@ -143,7 +223,12 @@ namespace com.keyman.renderer { }; this.arrayPromiseIteration(keyboardIterator, Object.keys(kbds).length).then(function() { - console.log("All keyboard renders are now complete!"); + // Once all renders are done, we can now tidy the page up and prep it for final display + potential file-saving. + + // This will go at the top of the page when finished, but not when actively rendering. + // We want to leave as much space visible as possible when actively rendering keyboards + // so that auto-scrolling isn't an issue. + this.fillDeviceNotes(); }); } else { console.error("KeymanWeb not detected!"); From 91c38ced7576829176f29f00328812c82793e38b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 17 Dec 2018 14:23:05 +0700 Subject: [PATCH 12/15] Extra bit of desktop vs mobile distinction. --- web/bulk_rendering/renderer_core.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index 371fc04550..d293fdd5f2 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -204,7 +204,9 @@ namespace com.keyman.renderer { // We want the renderer to control where the keyboard is displayed. // Also bypasses another 'fun' OSK complication. - keyman.osk.userPositioned = true; + if(keyman.util.device.formFactor == 'desktop') { + keyman.osk.userPositioned = true; + } // Assumes that the keyboards have been preloaded for us. let kbds = this.filterKeyboards(); From 27af75f3421478b6df0adebd46fa79726d7ecebf Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 18 Dec 2018 08:32:52 +0700 Subject: [PATCH 13/15] Fixes the touch bug - CSS 'opacity' settings were to blame. --- web/bulk_rendering/renderer_core.ts | 2 -- web/source/kmwosk.ts | 15 ++++++++++++++- web/testing/issue382/issue382.kpj | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 web/testing/issue382/issue382.kpj diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index d293fdd5f2..9b8d0d66ab 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -124,8 +124,6 @@ namespace com.keyman.renderer { // Make sure the active element's still set! renderer.setActiveDummy(); keyman.osk.show(true); - // Juuuust in case it's not otherwise showing. (Sometimes happens after scrolling on touch-devices.) - box.style.display = 'block'; renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { let eleLayer = document.createElement('div'); diff --git a/web/source/kmwosk.ts b/web/source/kmwosk.ts index bbcdfac6d0..3a62379338 100644 --- a/web/source/kmwosk.ts +++ b/web/source/kmwosk.ts @@ -4015,7 +4015,20 @@ if(!window['keyman']['initialized']) { var Ls = osk._Box.style; // Do not display OSK until it has been positioned correctly - if(device.touchable && Ls.bottom == '') Ls.visibility='hidden'; + if(device.touchable && Ls.bottom == '') { + Ls.visibility='hidden'; + } + + if(device.touchable) { + /* In case it's still '0' from a hide() operation. + * Happens when _Show is called before the transitionend events are processed, + * which can happen in bulk-rendering contexts. + * + * (Opacity is only modified when device.touchable = true, though a couple of extra + * conditions may apply.) + */ + Ls.opacity='1'; + } // The following code will always be executed except for externally created OSK such as EuroLatin if(osk.ddOSK) diff --git a/web/testing/issue382/issue382.kpj b/web/testing/issue382/issue382.kpj new file mode 100644 index 0000000000..45b8167c3d --- /dev/null +++ b/web/testing/issue382/issue382.kpj @@ -0,0 +1,20 @@ + + + + + False + True + + + + id_d6d0aefd0ac869e89e3e93e50e54f856 + issue382.kmn + issue382.kmn + 1.0 + .kmn +
+ Keycap Scaling Test +
+
+
+
From 7899278ace2377a561d1e0c4dfafd1fac396dae8 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 18 Dec 2018 08:36:26 +0700 Subject: [PATCH 14/15] A little bit of tidying. --- web/bulk_rendering/build.sh | 2 +- web/history.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/bulk_rendering/build.sh b/web/bulk_rendering/build.sh index 936b0d27c1..6b7006af15 100644 --- a/web/bulk_rendering/build.sh +++ b/web/bulk_rendering/build.sh @@ -1,6 +1,6 @@ #! /bin/bash # -# Compile the KeymanWeb Recorder module for use with developing/running engine tests. +# Compile the KeymanWeb bulk-renderer module for use with developing/running engine tests. # # Fails the build if a specified file does not exist. diff --git a/web/history.md b/web/history.md index 50589d2440..a456e7e161 100644 --- a/web/history.md +++ b/web/history.md @@ -4,6 +4,7 @@ * Add `setNumericLayer()` for embedded platforms to change OSK to numeric layer. * Fixes issue where file extensions are upper-case, e.g. ".TTF" * Fixes keyboard layout issues after mobile device rotations. (#248) (#970) +* Adds support for Promises to init() and setActiveKeyboard(). (#100) ## 2018-07-06 10.0.103 stable * Fixes issue for embedded Android, iOS apps where a keyboard with varying row counts in different layers could crash (#1055) From 86c88f2120b251728453b8b360ccf295bb5d13d0 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 18 Dec 2018 11:47:56 +0700 Subject: [PATCH 15/15] Fixes a scoping issue on the final render step - device notes. --- web/bulk_rendering/renderer_core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/bulk_rendering/renderer_core.ts b/web/bulk_rendering/renderer_core.ts index 9b8d0d66ab..3b3bfedaed 100644 --- a/web/bulk_rendering/renderer_core.ts +++ b/web/bulk_rendering/renderer_core.ts @@ -228,7 +228,7 @@ namespace com.keyman.renderer { // This will go at the top of the page when finished, but not when actively rendering. // We want to leave as much space visible as possible when actively rendering keyboards // so that auto-scrolling isn't an issue. - this.fillDeviceNotes(); + renderer.fillDeviceNotes(); }); } else { console.error("KeymanWeb not detected!");