summaryrefslogtreecommitdiff
path: root/extsrc/pikchr-worker.js
diff options
context:
space:
mode:
Diffstat (limited to 'extsrc/pikchr-worker.js')
-rw-r--r--extsrc/pikchr-worker.js222
1 files changed, 222 insertions, 0 deletions
diff --git a/extsrc/pikchr-worker.js b/extsrc/pikchr-worker.js
new file mode 100644
index 0000000..2df5bb5
--- /dev/null
+++ b/extsrc/pikchr-worker.js
@@ -0,0 +1,222 @@
+/*
+ 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('<div'));
+ if(msg.isError){
+ msg.width = msg.height = null;
+ }else{
+ msg.width = pikchrModule.getValue(pnWidth, "i32");
+ msg.height = pikchrModule.getValue(pnHeight, "i32");
+ }
+ wMsg('pikchr', msg);
+ } finally {
+ pikchrModule.stackRestore(stack);
+ wMsg('working','end');
+ }
+ return;
+ };
+ console.warn("Unknown pikchr-worker message type:",ev);
+ };
+
+ /**
+ emscripten module for use with build mode -sMODULARIZE.
+ */
+ const pikchrModule = {
+ print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));},
+ printErr: stderr,
+ /**
+ Intercepts status updates from the emscripting module init
+ and fires worker events with a type of 'status' and a
+ payload of:
+
+ {
+ text: string | null, // null at end of load process
+ step: integer // starts at 1, increments 1 per call
+ }
+
+ We have no way of knowing in advance how many steps will
+ be processed/posted, so creating a "percentage done" view is
+ not really practical. One can be approximated by giving it a
+ current value of message.step and max value of message.step+1,
+ though.
+
+ When work is finished, a message with a text value of null is
+ submitted.
+
+ After a message with text==null is posted, the module may later
+ post messages about fatal problems, e.g. an exit() being
+ triggered, so it is recommended that UI elements for posting
+ status messages not be outright removed from the DOM when
+ text==null, and that they instead be hidden until/unless
+ text!=null.
+ */
+ setStatus: function f(text){
+ if(!f.last) f.last = { step: 0, text: '' };
+ else if(text === f.last.text) return;
+ f.last.text = text;
+ wMsg('module',{
+ type:'status',
+ data:{step: ++f.last.step, text: text||null}
+ });
+ }
+ };
+
+ importScripts('pikchr-v8806526039.js');
+ /**
+ initPikchrModule() is installed via pikchr.js due to
+ building with:
+
+ emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule
+ */
+ initPikchrModule(pikchrModule).then(function(thisModule){
+ //globalThis.M = pikchrModule; console.warn("pikchrModule=globalThis.M=",globalThis.M);
+ wMsg('pikchr-ready', pikchrModule.ccall('pikchr_version','string'));
+ });
+})();