/* 2022-05-20 The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. *********************************************************************** This is a JS Worker file for use with the pikchr wasm build. It loads the pikchr wasm module and offers access to it via the Worker message-passing interface. Because we can have only a single message handler, as opposed to an arbitrary number of discrete event listeners like with DOM elements, we have to define a lower-level message API. Messages abstractly look like: { type: string, data: type-specific value } Where 'type' is used for dispatching and 'data' is a 'type'-dependent value. The 'type' values expected by each side of the main/worker connection vary. The types are described below but subject to change at any time as this experiment evolves. Main-to-Worker message types: - pikchr: data=pikchr-format text to render or an object: { pikchr: source code for the pikchr, darkMode: boolean true to adjust colors for a dark color scheme, cssClass: CSS class name to add to the SVG } Workers-to-Main message types: - stdout, stderr: indicate stdout/stderr output from the wasm layer. The data property is the string of the output, noting that the emscripten binding emits these one line at a time. Thus, if a C-side puts() emits multiple lines in a single call, the JS side will see that as multiple calls. Example: {type:'stdout', data: 'Hi, world.'} - module: Status text. This is intended to alert the main thread about module loading status so that, e.g., the main thread can update a progress widget and DTRT when the module is finished loading and available for work. Status messages come in the form {type:'module', data:{ type:'status', data: {text:string|null, step:1-based-integer} } with an incrementing step value for each subsequent message. When the module loading is complete, a message with a text value of null is posted. - pikchr: {type: 'pikchr', data:{ pikchr: input text, result: rendered result (SVG on success, HTML on error), isError: bool, true if .pikchr holds an error report, flags: integer: flags used to configure the pikchr rendering, width: if !isError, width (integer pixels) of the SVG, height: if !isError, height (integer pixels) of the SVG } } */ "use strict"; (function(){ /** Posts a message in the form {type,data} unless passed more than 2 args, in which case it posts {type, data:[arg1...argN]}. */ const wMsg = function(type,data){ postMessage({ type, data: arguments.length<3 ? data : Array.prototype.slice.call(arguments,1) }); }; const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));}; self.onerror = function(/*message, source, lineno, colno, error*/) { const err = arguments[4]; if(err && 'ExitStatus'==err.name){ /* This "cannot happen" for this wasm binding, but just in case... */ pikchrModule.isDead = true; stderr("FATAL ERROR:", err.message); stderr("Restarting the app requires reloading the page."); wMsg('error', err); } pikchrModule.setStatus('Exception thrown, see JavaScript console: '+err); }; self.onmessage = function f(ev){ ev = ev.data; switch(ev.type){ /** Runs the given text through pikchr and emits a 'pikchr' message result (output format documented above). Fires a working/start event before it starts and working/end event when it finishes. */ case 'pikchr': if(pikchrModule.isDead){ stderr("wasm module has exit()ed. Cannot pikchr."); return; } if(!f._){ f._ = pikchrModule.cwrap('pikchr', 'string', [ 'string'/*script*/, 'string'/*CSS class*/, 'number'/*flags*/, 'number'/*output: SVG width*/, 'number'/*output: SVG height*/ ]); } wMsg('working','start'); const stack = pikchrModule.stackSave(); try { const pnWidth = pikchrModule.stackAlloc(4), pnHeight = pikchrModule.stackAlloc(4); let script = '', flags = 0, cssClass = null; if('string'===typeof ev.data){ script = ev.data; }else if(ev.data && 'object'===typeof ev.data){ script = ev.data.pikchr; flags = ev.data.darkMode ? 0x02 : 0; if(ev.data.cssClass) cssClass = ev.data.cssClass; } pikchrModule.setValue(pnWidth, 0, "i32"); pikchrModule.setValue(pnHeight, 0, "i32"); const msg = { pikchr: script, result: (f._(script, cssClass, flags, pnWidth, pnHeight) || "").trim(), flags: flags }; msg.isError = !!(msg.result && msg.result.startsWith('