Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor code to allow TikZ to html conversions on demand #30

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
package-lock.json
*.swp
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ Then in the `<body>`, include TikZ code such as
Your TikZ will be compiled into SVGs; the `<script>` element will be
replaced with the corresponding SVG.

### Running on demand

By default, `<script>` elements will be replaced when the `window.onload` event
executes. This is preventable by setting `window.TikZJaxNoAutostart = true`. You
can then run the replace logic manually with `window.TikZJax(document)`.

You can also convert TikZ code directly to html with `window.LoadTikZJax`. For
example:

```js
const tex = await window.LoadTikZJax("path to wasm", "path to coredump");
const { machine, html } = await tex("\begin{tikzpicture} ... \end{tikzpicture});
```

This is what `window.TikZJax` does in addition to replacing the `<script>`
elements with `html`.

## How does this work?

Using https://github.com/kisonecat/web2js the Pascal source of `tex`
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"dependencies": {
"browserfs": "^2.0.0",
"dvi2html": "0.0.2",
"express": "^4.16.4",
"fetch-readablestream": "^0.2.0",
"pako": "^1.0.8",
"web-streams-polyfill": "^1.3.2"
Expand Down
190 changes: 71 additions & 119 deletions src/TikZJax.js
Original file line number Diff line number Diff line change
@@ -1,123 +1,75 @@
import { dvi2html } from 'dvi2html';
import { Writable } from 'stream';
import * as library from './library';
import pako from 'pako';
import fetchStream from 'fetch-readablestream';

// document.currentScript polyfill
if (document.currentScript === undefined) {
var scripts = document.getElementsByTagName('script');
document.currentScript = scripts[scripts.length - 1];
}

// Determine where we were loaded from; we'll use that to find a
// tikzwolke server that can handle our POSTing tikz code
var url = new URL(document.currentScript.src);
// host includes the port
var host = url.host;
var urlRoot = url.protocol + '//' + host;

let pages = 1000;
var coredump;
var code;

async function load() {
let tex = await fetch(urlRoot + '/tex.wasm');
code = await tex.arrayBuffer();

let response = await fetchStream(urlRoot + '/core.dump.gz');
const reader = response.body.getReader();
const inf = new pako.Inflate();

try {
while (true) {
const {done, value} = await reader.read();
inf.push(value, done);
if (done) break;
}
}
finally {
reader.releaseLock();
}

coredump = new Uint8Array( inf.result, 0, pages*65536 );
import { dvi2html } from "dvi2html";
import { Writable } from "stream";
import * as library from "./library";
import pako from "pako";
import fetchStream from "fetch-readablestream";

const pages = 1000;

export async function load(wasmUrl, coredumpUrl) {
const tex = await fetch(wasmUrl);
const code = await tex.arrayBuffer();

const response = await fetchStream(coredumpUrl);
const reader = response.body.getReader();
const inf = new pako.Inflate();

try {
while (true) {
const { done, value } = await reader.read();
inf.push(value, done);
if (done) break;
}
}
finally {
reader.releaseLock();
}

const coredump = new Uint8Array(inf.result, 0, pages*65536);
return async (input) => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load returns a function that sets up wasm with the loaded payload/core dumps, so theoretically you could load different wasm/coredumps.

const tex1 = await load(wasm1, coredump1);
const tex2 = await load(wasm2, coredump2);

tex1(tikz1);
tex2(tikz2);

The main point of this is to prevent loading multiple times though. I don't see any benefit to loading multiple times.

if (input.match("\\\\begin *{document}") === null) {
input = "\\begin{document}\n" + input;
}
input = input + "\n\\end{document}\n";

library.deleteEverything();
library.writeFileSync("sample.tex", Buffer.from(input));

const memory = new WebAssembly.Memory({ initial: pages, maximum: pages });

const buffer = new Uint8Array(memory.buffer, 0, pages*65536);
buffer.set(copy(coredump));

library.setMemory(memory.buffer);
library.setInput(" sample.tex \n\\end\n");

await WebAssembly.instantiate(code, {
library: library,
env: { memory: memory }
});

const dvi = library.readFileSync("sample.dvi");

let html = "";
const page = new Writable({
write(chunk, _, callback) {
html = html + chunk.toString();
callback();
}
});

async function* streamBuffer() {
yield Buffer.from(dvi);
return;
}
const machine = await dvi2html(streamBuffer(), page);

return { machine, html };
};
}

function copy(src) {
var dst = new Uint8Array(src.length);
dst.set(src);
return dst;
}

async function tex(input) {
if (input.match('\\\\begin *{document}') === null) {
input = '\\begin{document}\n' + input;
}
input = input + '\n\\end{document}\n';

library.deleteEverything();
library.writeFileSync( "sample.tex", Buffer.from(input) );

let memory = new WebAssembly.Memory({initial: pages, maximum: pages});

let buffer = new Uint8Array( memory.buffer, 0, pages*65536 );
buffer.set( copy(coredump) );

library.setMemory( memory.buffer );
library.setInput( " sample.tex \n\\end\n" );

let results = await WebAssembly.instantiate(code, { library: library,
env: { memory: memory }
});

return library.readFileSync( "sample.dvi" );
const dst = new Uint8Array(src.length);
dst.set(src);
return dst;
}

export async function TikZJax(root){
await load();

async function process(elt){
var text = elt.childNodes[0].nodeValue;

var div = document.createElement('div');

let dvi = await tex(text);

let html = "";
const page = new Writable({
write(chunk, _, callback) {
html = html + chunk.toString();
callback();
}
});

async function* streamBuffer() {
yield Buffer.from(dvi);
return;
}

let machine = await dvi2html( streamBuffer(), page );
div.style.display = 'flex';
div.style.width = machine.paperwidth.toString() + "pt";
div.style.height = machine.paperheight.toString() + "pt";
div.style['align-items'] = 'center';
div.style['justify-content'] = 'center';

div.innerHTML = html;
let svg = div.getElementsByTagName('svg');
svg[0].setAttribute("width", machine.paperwidth.toString() + "pt");
svg[0].setAttribute("height", machine.paperheight.toString() + "pt");
svg[0].setAttribute("viewBox", `-72 -72 ${machine.paperwidth} ${machine.paperheight}`);

elt.parentNode.replaceChild(div, elt);
};

var scripts = root.getElementsByTagName('script');
var tikzScripts = Array.prototype.slice.call(scripts).filter(
(e) => (e.getAttribute('type') === 'text/tikz'));

tikzScripts.reduce( async (promise, element) => {
await promise;
return process(element);
}, Promise.resolve());
};
56 changes: 52 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
import { TikZJax } from "./TikZJax";
import { load } from "./TikZJax";

// document.currentScript polyfill
if (document.currentScript === undefined) {
const scripts = document.getElementsByTagName("script");
document.currentScript = scripts[scripts.length - 1];
}
const url = new URL(document.currentScript.src);

// Determine where we were loaded from; we"ll use that to find a
// tikzwolke server that can handle our POSTing tikz code
// host includes the port
const host = url.host;
const urlRoot = url.protocol + "//" + host;

window.TikZJax = async function TikZJax(root){
const tex = await load(urlRoot + "/tex.wasm", urlRoot + "/core.dump.gz");

async function process(elt){
const text = elt.childNodes[0].nodeValue;
const { machine, html } = await tex(text);

const div = document.createElement("div");

div.style.display = "flex";
div.style.width = machine.paperwidth.toString() + "pt";
div.style.height = machine.paperheight.toString() + "pt";
div.style["align-items"] = "center";
div.style["justify-content"] = "center";

div.innerHTML = html;
const svg = div.getElementsByTagName("svg");
svg[0].setAttribute("width", machine.paperwidth.toString() + "pt");
svg[0].setAttribute("height", machine.paperheight.toString() + "pt");
svg[0].setAttribute("viewBox", `-72 -72 ${machine.paperwidth} ${machine.paperheight}`);

elt.parentNode.replaceChild(div, elt);
};

const scripts = root.getElementsByTagName("script");
const tikzScripts = Array.prototype.slice.call(scripts).filter(
(e) => (e.getAttribute("type") === "text/tikz"));

tikzScripts.reduce(async (promise, element) => {
await promise;
return process(element);
}, Promise.resolve());
}

window.LoadTikZJax = load;

window.TikZJax = TikZJax;
if (!window.TikZJaxNoAutostart) {
window.onload = TikZJax(document);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is considered a bug or not, but TikZJax was not being executed with the onload function, but was being executed when the line was hit (because the result of the call is what's being assigned to window.onload)

}
window.onload = () => TikZJax(document);
}