mirror of
https://github.com/jgm/pandoc.git
synced 2026-09-01 13:15:51 +00:00
168 lines
6.1 KiB
JavaScript
168 lines
6.1 KiB
JavaScript
/* pandoc.js: JavaScript interface to pandoc.wasm.
|
|
Copyright (c) 2025 Tweag I/O Limited and John MacFarlane. MIT License.
|
|
|
|
Interface:
|
|
|
|
await convert(options, stdin, files)
|
|
|
|
- options is a JavaScript object representing pandoc options: this should
|
|
correspond to the format used in pandoc's default files.
|
|
- stdin is a string or nil
|
|
- files is a JavaScript object whose keys are filenames and whose values
|
|
are the data in the corresponding file, as Blobs.
|
|
|
|
The return value is a JavaScript object with 3 properties, stdout,
|
|
stderr, and warnings, all strings. warnings is a JSON-encoded
|
|
version of the warnings produced by pandoc. If the pandoc process
|
|
produces an output file, it will be added to files.
|
|
|
|
await query(options)
|
|
|
|
- options is a JavaScript object with a 'query' property and in
|
|
some cases a 'format' property. Possible queries include
|
|
'version', 'highlight-styles', 'highlight-languages', 'input-formats',
|
|
'output-formats', 'default-template' (requires 'format'),
|
|
and 'extensions-for-format' (requires 'format').
|
|
|
|
The return value is a JavaScript string or in some cases a list
|
|
of strings.
|
|
*/
|
|
|
|
import {
|
|
WASI,
|
|
OpenFile,
|
|
File,
|
|
ConsoleStdout,
|
|
PreopenDirectory,
|
|
} from "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.3.0/dist/index.js";
|
|
|
|
const args = ["pandoc.wasm", "+RTS", "-H64m", "-RTS"];
|
|
const env = [];
|
|
const in_file = new File(new Uint8Array(), { readonly: true });
|
|
const out_file = new File(new Uint8Array(), { readonly: false });
|
|
const err_file = new File(new Uint8Array(), { readonly: false });
|
|
const warnings_file = new File(new Uint8Array(), { readonly: false });
|
|
const fileSystem = new Map();
|
|
const fds = [
|
|
new OpenFile(new File(new Uint8Array(), { readonly: true })),
|
|
ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stdout] ${msg}`)),
|
|
ConsoleStdout.lineBuffered((msg) => console.warn(`[WASI stderr] ${msg}`)),
|
|
new PreopenDirectory("/", fileSystem),
|
|
];
|
|
const options = { debug: false };
|
|
const wasi = new WASI(args, env, fds, options);
|
|
const { instance } = await WebAssembly.instantiateStreaming(
|
|
fetch("./pandoc.wasm?sha1=SHA1_PANDOC_WASM"),
|
|
{
|
|
wasi_snapshot_preview1: wasi.wasiImport,
|
|
}
|
|
);
|
|
|
|
wasi.initialize(instance);
|
|
instance.exports.__wasm_call_ctors();
|
|
|
|
function memory_data_view() {
|
|
return new DataView(instance.exports.memory.buffer);
|
|
}
|
|
|
|
const argc_ptr = instance.exports.malloc(4);
|
|
memory_data_view().setUint32(argc_ptr, args.length, true);
|
|
const argv = instance.exports.malloc(4 * (args.length + 1));
|
|
for (let i = 0; i < args.length; ++i) {
|
|
const arg = instance.exports.malloc(args[i].length + 1);
|
|
new TextEncoder().encodeInto(
|
|
args[i],
|
|
new Uint8Array(instance.exports.memory.buffer, arg, args[i].length)
|
|
);
|
|
memory_data_view().setUint8(arg + args[i].length, 0);
|
|
memory_data_view().setUint32(argv + 4 * i, arg, true);
|
|
}
|
|
memory_data_view().setUint32(argv + 4 * args.length, 0, true);
|
|
const argv_ptr = instance.exports.malloc(4);
|
|
memory_data_view().setUint32(argv_ptr, argv, true);
|
|
|
|
instance.exports.hs_init_with_rtsopts(argc_ptr, argv_ptr);
|
|
|
|
export async function query(options) {
|
|
const opts_str = JSON.stringify(options);
|
|
const opts_bytes = new TextEncoder().encode(opts_str);
|
|
const opts_ptr = instance.exports.malloc(opts_bytes.length);
|
|
new Uint8Array(instance.exports.memory.buffer, opts_ptr, opts_bytes.length)
|
|
.set(opts_bytes);
|
|
// add input files to fileSystem
|
|
fileSystem.clear()
|
|
const out_file = new File(new Uint8Array(), { readonly: false });
|
|
const err_file = new File(new Uint8Array(), { readonly: false });
|
|
fileSystem.set("stdout", out_file);
|
|
fileSystem.set("stderr", err_file);
|
|
instance.exports.query(opts_ptr, opts_bytes.length);
|
|
|
|
const err_text = new TextDecoder("utf-8", { fatal: true }).decode(err_file.data);
|
|
if (err_text) console.log(err_text);
|
|
const out_text = new TextDecoder("utf-8", { fatal: true }).decode(out_file.data);
|
|
return JSON.parse(out_text);
|
|
}
|
|
|
|
|
|
export async function convert(options, stdin, files) {
|
|
const opts_str = JSON.stringify(options);
|
|
const opts_bytes = new TextEncoder().encode(opts_str);
|
|
const opts_ptr = instance.exports.malloc(opts_bytes.length);
|
|
new Uint8Array(instance.exports.memory.buffer, opts_ptr, opts_bytes.length)
|
|
.set(opts_bytes);
|
|
// add input files to fileSystem
|
|
fileSystem.clear()
|
|
const in_file = new File(new Uint8Array(), { readonly: true });
|
|
const out_file = new File(new Uint8Array(), { readonly: false });
|
|
const err_file = new File(new Uint8Array(), { readonly: false });
|
|
const warnings_file = new File(new Uint8Array(), { readonly: false });
|
|
fileSystem.set("stdin", in_file);
|
|
fileSystem.set("stdout", out_file);
|
|
fileSystem.set("stderr", err_file);
|
|
fileSystem.set("warnings", warnings_file);
|
|
for (const file in files) {
|
|
await addFile(file, files[file], true);
|
|
}
|
|
// add output file if any
|
|
if (options["output-file"]) {
|
|
await addFile(options["output-file"], new Blob(), false);
|
|
}
|
|
// add media file for extracted media
|
|
if (options["extract-media"]) {
|
|
await addFile(options["extract-media"], new Blob(), false);
|
|
}
|
|
if (stdin) {
|
|
in_file.data = new TextEncoder().encode(stdin);
|
|
}
|
|
instance.exports.convert(opts_ptr, opts_bytes.length);
|
|
|
|
if (options["output-file"]) {
|
|
files[options["output-file"]] =
|
|
new Blob([fileSystem.get(options["output-file"]).data]);
|
|
}
|
|
if (options["extract-media"]) {
|
|
const mediaFile = fileSystem.get(options["extract-media"]);
|
|
if (mediaFile && mediaFile.data && mediaFile.data.length > 0) {
|
|
files[options["extract-media"]] =
|
|
new Blob([mediaFile.data], { type: 'application/zip' });
|
|
}
|
|
}
|
|
const rawWarnings = new TextDecoder("utf-8", { fatal: true })
|
|
.decode(warnings_file.data);
|
|
let warnings = [];
|
|
if (rawWarnings) {
|
|
warnings = JSON.parse(rawWarnings);
|
|
}
|
|
return {
|
|
stdout: new TextDecoder("utf-8", { fatal: true }).decode(out_file.data),
|
|
stderr: new TextDecoder("utf-8", { fatal: true }).decode(err_file.data),
|
|
warnings: warnings
|
|
};
|
|
}
|
|
|
|
async function addFile(filename, blob, readonly) {
|
|
const buffer = await blob.arrayBuffer();
|
|
const file = new File(new Uint8Array(buffer), { readonly: readonly });
|
|
fileSystem.set(filename, file);
|
|
}
|