1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
/* pandoc.js: JavaScript interface to pandoc.wasm.
Copyright (c) 2025 Tweag I/O Limited and John MacFarlane. MIT License.
Interface: await pandoc(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.
*/
import {
WASI,
OpenFile,
File,
ConsoleStdout,
PreopenDirectory,
} from "https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/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=XX"),
{
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 getExtensionsForFormat(options) {
const opts_str = JSON.stringify(options);
const opts_ptr = instance.exports.malloc(opts_str.length);
new TextEncoder().encodeInto(
opts_str,
new Uint8Array(instance.exports.memory.buffer, opts_ptr, opts_str.length)
);
// 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.get_extensions_for_format(opts_ptr, opts_str.length);
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(out_file.data));
}
export async function pandoc(options, stdin, files) {
const opts_str = JSON.stringify(options);
const opts_ptr = instance.exports.malloc(opts_str.length);
new TextEncoder().encodeInto(
opts_str,
new Uint8Array(instance.exports.memory.buffer, opts_ptr, opts_str.length)
);
// 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.wasm_main(opts_ptr, opts_str.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/x-tar' });
}
}
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);
}
|