summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2018-09-10 18:18:49 +0200
committerTor Andersson <tor.andersson@artifex.com>2018-10-23 17:23:59 +0200
commit8ebce9c149112d59552ed530361f80372455fdb2 (patch)
treeebc90eb93345d1b3a77841ed2a92e56a4c1b1baf /platform
parentddd00f62888c908c84932c2a92a0c2d195b26c36 (diff)
downloadmupdf-8ebce9c149112d59552ed530361f80372455fdb2.tar.xz
Add Emscripten / WebAssembly build.
Requires Linux (or possibly MacOS X) and an installed emsdk to build.
Diffstat (limited to 'platform')
-rw-r--r--platform/wasm/Makefile2
-rw-r--r--platform/wasm/build.sh28
-rw-r--r--platform/wasm/readme.html135
-rw-r--r--platform/wasm/view-as-html.html45
-rw-r--r--platform/wasm/view-as-svg.html44
-rw-r--r--platform/wasm/view-page.html74
-rw-r--r--platform/wasm/view.html84
-rw-r--r--platform/wasm/wrap.c247
-rw-r--r--platform/wasm/wrap.js51
9 files changed, 710 insertions, 0 deletions
diff --git a/platform/wasm/Makefile b/platform/wasm/Makefile
new file mode 100644
index 00000000..fcf5b24f
--- /dev/null
+++ b/platform/wasm/Makefile
@@ -0,0 +1,2 @@
+default:
+ bash build.sh
diff --git a/platform/wasm/build.sh b/platform/wasm/build.sh
new file mode 100644
index 00000000..ee092dc8
--- /dev/null
+++ b/platform/wasm/build.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+make -j4 -C ../.. generate
+
+source /opt/emsdk/emsdk_env.sh
+
+echo Building library:
+make -j4 -C ../.. \
+ OS=wasm build=release \
+ XCFLAGS="-DTOFU -DTOFU_CJK -DFZ_ENABLE_SVG=0 -DFZ_ENABLE_HTML=0 -DFZ_ENABLE_EPUB=0 -DFZ_ENABLE_JS=0" \
+ libs
+
+echo
+echo Linking WebAssembly:
+emcc -Wall -Os -o libmupdf.js \
+ -s WASM=1 \
+ -s VERBOSE=0 \
+ -s ABORTING_MALLOC=0 \
+ -s TOTAL_MEMORY=134217728 \
+ -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
+ -s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE='[$Browser,"memcpy","memset","malloc","free"]' \
+ -I ../../include \
+ --pre-js wrap.js \
+ wrap.c \
+ ../../build/wasm/release/libmupdf.a \
+ ../../build/wasm/release/libmupdf-third.a
+
+echo Done.
diff --git a/platform/wasm/readme.html b/platform/wasm/readme.html
new file mode 100644
index 00000000..5d6b97ed
--- /dev/null
+++ b/platform/wasm/readme.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>MuPDF / WebAssembly</title>
+<meta charset="utf-8">
+<style>
+pre,dl{margin-left:2em;}
+dt{margin-top:0.5em;font-family:monospace}
+</style>
+</head>
+<body>
+
+<h1>MuPDF WebAssembly</h1>
+
+<h2>Building</h2>
+
+<p>
+The WebAssembly build has only been tested on Linux at the moment.
+If you use any other platform, you are on your own.
+
+<p>
+In order to build this you will need to install the
+<a href="https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html">Emscripten SDK</a>
+in <tt>/opt/emsdk</tt>.
+If you install it elsewhere, you will need to edit the platform/wasm/build.sh
+script to point to the appropriate location.
+
+<p>
+From the MuPDF project, you can run <tt>make wasm</tt> to build the WebAssembly
+library. The results of the build are a libmupdf.wasm binary and
+libmupdf.js script, placed in platform/wasm/.
+
+<p>
+In order to build a web application based on MuPDF, you will need to copy
+these two files and make them available to your page.
+
+<p>
+The libmupdf.wasm binary is quite large, because it contains not only the MuPDF
+library code, but also the 14 core PDF fonts, various CJK mapping resources,
+and ICC profiles. In order to keep it as small as possible, it is built with a
+minimal features set that does not include CJK fonts, EPUB support, etc.
+
+<h2>Using</h2>
+
+<p>
+The example script in platform/wasm/view.html shows how to use the
+MuPDF WebAssembly library.
+
+<p>
+The first part is including the libmupdf.js script, which pulls in
+and instantiates the WebAssembly module:
+
+<pre>
+&lt;script src="libmupdf.js"&gt;&lt;/script&gt;
+</pre>
+
+<p>
+MuPDF uses the Emscripten virtual file system to load a document, so you will
+need to seed it with the file you want to view in a Module.preRun
+callback function:
+
+<pre>
+&lt;script&gt;
+Module.preRun = function () {
+ FS.createPreloadedFile(".", "input.pdf", "input.pdf", true, false);
+}
+&lt;/script&gt;
+</pre>
+
+<p>
+When the filesystem has finished preloading the file data and initialized the
+code, it will call the Module.postRun callback. From here, you can
+use the 'mupdf' object to call various functions to open the document and
+render pages into various formats.
+
+<dl>
+<dt>mupdf.openDocument(filename)
+<dd>Open a document and return a handle.
+<dt>mupdf.freeDocument(doc)
+<dd>Free a document and its associated resources.
+<dt>mupdf.documentTitle(doc)
+<dd>Return the document title as a string.
+<dt>mupdf.documentOutline(doc)
+<dd>Return a DOM element containing the table of contents formatted as
+an unordered HTML list with links to pages using anchor fragments "#page%d".
+<dt>mupdf.countPages(doc)
+<dd>Return the number of pages in the document.
+<dt>mupdf.pageWidth(doc, page, dpi) and mupdf.pageHeight(doc, page, dpi)
+<dd>Return the dimensions of a page. Page numbering starts at 1.
+<dt>mupdf.drawPageAsPNG(doc, page, dpi)
+<dd>Render the page and return a PNG image formatted as a data URI,
+suitable for using as an image source attribute.
+<dt>mupdf.pageLinks(doc, page, dpi)
+<dd>Retrieve an HTML string describing the links on a page,
+suitable for including as the innerHTML of an image map.
+<dt>mupdf.drawPageAsSVG(doc, page)
+<dd>Return a string with the contents of the page in SVG format.
+<dt>mupdf.drawPageAsHTML(doc, page)
+<dd>Return a string with the contents of the page in HTML format,
+using absolute positioned elements.
+</dl>
+
+<h2>Example</h2>
+
+<p>
+Here is a very simple example of loading a document and drawing its first page:
+
+<pre>
+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;script src="libmupdf.js"&gt;&lt;/script&gt;
+&lt;script&gt;
+Module.preRun = function () {
+ FS.createPreloadedFile(".", "input.pdf", "input.pdf", true, false);
+}
+Module.postRun = function () {
+ var DPI = 96;
+ var doc = mupdf.openDocument("input.pdf");
+ var img = document.getElementById("page1");
+ img.src = mupdf.drawPageAsPNG(doc, 1, DPI);
+ var map = document.getElementById("page1map");
+ map.innerHTML = mupdf.pageLinks(doc, 1, DPI);
+}
+&lt;/script&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;img id="page1" usemap="#page1map"&gt;
+&lt;map id="page1map" name="page1map"&gt;&lt;/map&gt;
+&lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+</body>
+</html>
diff --git a/platform/wasm/view-as-html.html b/platform/wasm/view-as-html.html
new file mode 100644
index 00000000..d31d23a2
--- /dev/null
+++ b/platform/wasm/view-as-html.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Loading...</title>
+<meta charset="utf-8">
+<style>
+html,body,table,tr,td,div{background-color:gray;margin:0;padding:0;}
+ul{margin:0;padding-left:1em;}
+#outline{background-color:silver;padding:1em;padding-left:2em;}
+#pages{margin:0em;width:100%;}
+.page>div{margin:1em auto;}
+</style>
+<script src="libmupdf.js"></script>
+<script>
+var filename = new URL(window.location.href).searchParams.get("file");
+if (!filename)
+ filename = "pdfref13.pdf";
+Module.preRun = function () {
+ FS.createPreloadedFile(".", filename, filename, true, false);
+};
+Module.postRun = function () {
+ var currentDocument = null;
+ var pageCount = 0;
+ var currentPage = 1;
+ function loadNextPage() {
+ var element = document.createElement('div');
+ element.id = 'page' + currentPage;
+ element.className = 'page';
+ element.innerHTML = mupdf.drawPageAsHTML(currentDocument, currentPage);
+ element.getElementsByTagName("div")[0].style.backgroundImage = 'url(' + mupdf.drawPage(currentDocument, currentPage, 96) + ')'
+ document.getElementById("pages").appendChild(element);
+ if (++currentPage <= pageCount)
+ setTimeout(loadNextPage, 0);
+ }
+ currentDocument = mupdf.openDocument(filename);
+ document.title = mupdf.documentTitle(currentDocument);
+ pageCount = mupdf.countPages(currentDocument);
+ loadNextPage();
+}
+</script>
+</head>
+<body>
+<div id="pages"></div>
+</body>
+</html>
diff --git a/platform/wasm/view-as-svg.html b/platform/wasm/view-as-svg.html
new file mode 100644
index 00000000..4781751d
--- /dev/null
+++ b/platform/wasm/view-as-svg.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Loading...</title>
+<meta charset="utf-8">
+<style>
+html,body,table,tr,td,div{background-color:gray;margin:0;padding:0;}
+ul{margin:0;padding-left:1em;}
+#outline{background-color:silver;padding:1em;padding-left:2em;}
+#pages{margin:0em;width:100%;}
+.page>svg{margin:1em 1em;background-color:white;}
+</style>
+<script src="libmupdf.js"></script>
+<script>
+var filename = new URL(window.location.href).searchParams.get("file");
+if (!filename)
+ filename = "pdfref13.pdf";
+Module.preRun = function () {
+ FS.createPreloadedFile(".", filename, filename, true, false);
+};
+Module.postRun = function () {
+ var currentDocument = null;
+ var pageCount = 0;
+ var currentPage = 1;
+ function loadNextPage() {
+ var element = document.createElement('div');
+ element.id = 'page' + currentPage;
+ element.className = 'page';
+ element.innerHTML = mupdf.drawPageAsSVG(currentDocument, currentPage);
+ document.getElementById("pages").appendChild(element);
+ if (++currentPage <= pageCount)
+ setTimeout(loadNextPage, 0);
+ }
+ currentDocument = mupdf.openDocument(filename);
+ document.title = mupdf.documentTitle(currentDocument);
+ pageCount = mupdf.countPages(currentDocument);
+ loadNextPage();
+}
+</script>
+</head>
+<body>
+<div id="pages"></div>
+</body>
+</html>
diff --git a/platform/wasm/view-page.html b/platform/wasm/view-page.html
new file mode 100644
index 00000000..ce56962f
--- /dev/null
+++ b/platform/wasm/view-page.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Loading...</title>
+<meta charset="utf-8">
+<script src="libmupdf.js"></script>
+<script>
+var filename = new URL(window.location.href).searchParams.get("file");
+if (!filename)
+ filename = "pdfref13.pdf";
+Module.preRun = function () {
+ FS.createPreloadedFile(".", filename, filename, true, false);
+};
+Module.postRun = function () {
+ loadDocument(filename);
+};
+
+var currentDocument = null;
+var currentPage = 1;
+var currentZoom = 72;
+var pageCount = 0;
+function loadDocument(filename) {
+ currentDocument = mupdf.openDocument(filename);
+ document.title = mupdf.documentTitle(currentDocument);
+ pageCount = mupdf.countPages(currentDocument);
+ updatePage();
+}
+function updatePage() {
+ document.getElementById("page").src = mupdf.drawPageAsPNG(currentDocument, currentPage, currentZoom);
+ document.getElementById("pageNumber").value = currentPage;
+}
+function nextPage() {
+ if (currentPage <= pageCount) {
+ ++currentPage;
+ updatePage();
+ }
+}
+function prevPage() {
+ if (currentPage > 1) {
+ --currentPage;
+ updatePage();
+ }
+}
+function gotoPage() {
+ var page = parseInt(document.getElementById("pageNumber").value)
+ if (page >= 1 && page <= pageCount) {
+ currentPage = page;
+ updatePage();
+ }
+}
+function zoomIn() {
+ currentZoom *= 1.2;
+ updatePage();
+}
+function zoomOut() {
+ currentZoom /= 1.2;
+ updatePage();
+}
+</script>
+<style>
+body{margin:2em;background-color:#aaa}
+</style>
+</head>
+<body>
+<p>
+<button onClick="prevPage()">&lt;</button>
+<input id="pageNumber" onChange="gotoPage()" type="text" size="5">
+<button onClick="nextPage()">&gt;</button>
+<button onClick="zoomIn()">+</button>
+<button onClick="zoomOut()">-</button>
+<p>
+<img id="page">
+</body>
+</html>
diff --git a/platform/wasm/view.html b/platform/wasm/view.html
new file mode 100644
index 00000000..0ff34916
--- /dev/null
+++ b/platform/wasm/view.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Loading...</title>
+<meta charset="utf-8">
+<style>
+body { background-color:gray; margin:0; padding:0; }
+#outline { position:fixed; top:0; left:0; width:20em; max-width:20em; height:100%; overflow-y:scroll; background-color:white; }
+#outline ul { margin:0; padding-left:1em; font-size:small; }
+#outline a { text-decoration:none; }
+#outline a:hover { text-decoration:underline; }
+#outline + #pages { margin-left:20em; }
+#pages img { display:block; margin:1em auto; background-color:white; }
+</style>
+<script src="libmupdf.js"></script>
+<script>
+
+var currentDocument = null
+var blankPages = [];
+var DPI = 96;
+
+var filename = new URL(window.location.href).searchParams.get("file");
+if (!filename)
+ filename = "pdfref13.pdf";
+
+Module.preRun = function () {
+ FS.createPreloadedFile(".", filename, filename, true, false);
+};
+
+Module.postRun = function () {
+ var pagesDiv = document.getElementById("pages");
+
+ console.log("mupdf: opening", filename);
+ currentDocument = mupdf.openDocument(filename);
+ document.title = mupdf.documentTitle(currentDocument);
+
+ var outline = mupdf.documentOutline(currentDocument);
+ if (outline) {
+ var outlineDiv = document.createElement("div");
+ outlineDiv.id = "outline";
+ outlineDiv.appendChild(outline);
+ document.body.insertBefore(outlineDiv, pagesDiv);
+ }
+
+ var i, n = mupdf.countPages(currentDocument);
+ for (i = 1; i <= n; ++i) {
+ var img = new Image();
+ img.id = "page" + i;
+ img.pageNumber = i;
+ img.width = mupdf.pageWidth(currentDocument, i, DPI);
+ img.height = mupdf.pageHeight(currentDocument, i, DPI);
+ img.useMap = "#map" + i;
+ var map = document.createElement("map");
+ map.name = "map" + i;
+ map.innerHTML = mupdf.pageLinks(currentDocument, i, DPI);
+ pagesDiv.appendChild(img);
+ pagesDiv.appendChild(map);
+ blankPages[i] = img;
+ }
+
+ console.log("mupdf: loaded", n, "pages");
+
+ document.onscroll();
+}
+
+document.onscroll = function () {
+ function isVisible(element) {
+ return ((window.pageYOffset + window.innerHeight) > element.offsetTop) &&
+ (window.pageYOffset < (element.offsetTop + element.scrollHeight));
+ }
+ var i, n = blankPages.length;
+ for (i = 1; i <= n; ++i) {
+ if (blankPages[i] && isVisible(blankPages[i])) {
+ console.log("mupdf: drawing page", i);
+ blankPages[i].src = mupdf.drawPageAsPNG(currentDocument, i, DPI);
+ blankPages[i] = undefined;
+ }
+ };
+}
+
+</script>
+</head>
+<body><div id="pages"></div></body>
+</html>
diff --git a/platform/wasm/wrap.c b/platform/wasm/wrap.c
new file mode 100644
index 00000000..d2a3379e
--- /dev/null
+++ b/platform/wasm/wrap.c
@@ -0,0 +1,247 @@
+#include "emscripten.h"
+#include "mupdf/fitz.h"
+
+static fz_context *ctx;
+
+EMSCRIPTEN_KEEPALIVE
+void initContext(void)
+{
+ ctx = fz_new_context(NULL, NULL, 100<<20);
+ fz_register_document_handlers(ctx);
+}
+
+EMSCRIPTEN_KEEPALIVE
+fz_document *openDocument(const char *filename)
+{
+ return fz_open_document(ctx, filename);
+}
+
+EMSCRIPTEN_KEEPALIVE
+void freeDocument(fz_document *doc)
+{
+ fz_drop_document(ctx, doc);
+}
+
+EMSCRIPTEN_KEEPALIVE
+int countPages(fz_document *doc)
+{
+ return fz_count_pages(ctx, doc);
+}
+
+static fz_page *lastPage = NULL;
+
+static void loadPage(fz_document *doc, int number)
+{
+ static fz_document *lastPageDoc = NULL;
+ static int lastPageNumber = -1;
+ if (lastPageNumber != number || lastPageDoc != doc)
+ {
+ if (lastPage)
+ fz_drop_page(ctx, lastPage);
+ lastPage = fz_load_page(ctx, doc, number-1);
+ lastPageDoc = doc;
+ lastPageNumber = number;
+ }
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *drawPageAsHTML(fz_document *doc, int number)
+{
+ static unsigned char *data = NULL;
+ fz_stext_page *text;
+ fz_buffer *buf;
+ fz_output *out;
+
+ fz_free(ctx, data);
+ data = NULL;
+
+ loadPage(doc, number);
+
+ buf = fz_new_buffer(ctx, 0);
+ {
+ out = fz_new_output_with_buffer(ctx, buf);
+ {
+ text = fz_new_stext_page_from_page(ctx, lastPage, NULL);
+ fz_print_stext_page_as_html(ctx, out, text);
+ fz_drop_stext_page(ctx, text);
+ }
+ fz_write_byte(ctx, out, 0);
+ fz_close_output(ctx, out);
+ fz_drop_output(ctx, out);
+ }
+ fz_buffer_extract(ctx, buf, &data);
+ fz_drop_buffer(ctx, buf);
+
+ return (char*)data;
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *drawPageAsSVG(fz_document *doc, int number)
+{
+ static int id = 0;
+ static unsigned char *data = NULL;
+ fz_buffer *buf;
+ fz_output *out;
+ fz_device *dev;
+ fz_rect bbox;
+
+ fz_free(ctx, data);
+ data = NULL;
+
+ loadPage(doc, number);
+
+ buf = fz_new_buffer(ctx, 0);
+ {
+ out = fz_new_output_with_buffer(ctx, buf);
+ {
+ bbox = fz_bound_page(ctx, lastPage);
+ dev = fz_new_svg_device(ctx, out, bbox.x1-bbox.x0, bbox.y1-bbox.y0, FZ_SVG_TEXT_AS_PATH, 0, &id);
+ fz_run_page(ctx, lastPage, dev, fz_identity, NULL);
+ fz_close_device(ctx, dev);
+ fz_drop_device(ctx, dev);
+ }
+ fz_write_byte(ctx, out, 0);
+ fz_close_output(ctx, out);
+ fz_drop_output(ctx, out);
+ }
+ fz_buffer_extract(ctx, buf, &data);
+ fz_drop_buffer(ctx, buf);
+
+ return (char*)data;
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *drawPageAsPNG(fz_document *doc, int number, float dpi)
+{
+ static unsigned char *data = NULL;
+ float zoom = dpi / 72;
+ fz_pixmap *pix;
+ fz_buffer *buf;
+ fz_output *out;
+
+ fz_free(ctx, data);
+ data = NULL;
+
+ loadPage(doc, number);
+
+ buf = fz_new_buffer(ctx, 0);
+ {
+ out = fz_new_output_with_buffer(ctx, buf);
+ {
+ pix = fz_new_pixmap_from_page(ctx, lastPage, fz_scale(zoom, zoom), fz_device_rgb(ctx), 0);
+ fz_write_pixmap_as_data_uri(ctx, out, pix);
+ fz_drop_pixmap(ctx, pix);
+ }
+ fz_write_byte(ctx, out, 0);
+ fz_close_output(ctx, out);
+ fz_drop_output(ctx, out);
+ }
+ fz_buffer_extract(ctx, buf, &data);
+ fz_drop_buffer(ctx, buf);
+
+ return (char*)data;
+}
+
+static fz_irect pageBounds(fz_document *doc, int number, float dpi)
+{
+ loadPage(doc, number);
+ return fz_round_rect(fz_transform_rect(fz_bound_page(ctx, lastPage), fz_scale(dpi/72, dpi/72)));
+}
+
+EMSCRIPTEN_KEEPALIVE
+int pageWidth(fz_document *doc, int number, float dpi)
+{
+ fz_irect bbox = pageBounds(doc, number, dpi);
+ return bbox.x1 - bbox.x0;
+}
+
+EMSCRIPTEN_KEEPALIVE
+int pageHeight(fz_document *doc, int number, float dpi)
+{
+ fz_irect bbox = pageBounds(doc, number, dpi);
+ return bbox.y1 - bbox.y0;
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *pageLinks(fz_document *doc, int number, float dpi)
+{
+ static unsigned char *data = NULL;
+ fz_buffer *buf;
+ fz_link *links, *link;
+
+ fz_free(ctx, data);
+ data = NULL;
+
+ loadPage(doc, number);
+
+ buf = fz_new_buffer(ctx, 0);
+ {
+ links = fz_load_links(ctx, lastPage);
+ {
+ for (link = links; link; link = link->next)
+ {
+ fz_irect bbox = fz_round_rect(fz_transform_rect(link->rect, fz_scale(dpi/72, dpi/72)));
+ fz_append_printf(ctx, buf, "<area shape=\"rect\" coords=\"%d,%d,%d,%d\"",
+ bbox.x0, bbox.y0, bbox.x1, bbox.y1);
+ if (fz_is_external_link(ctx, link->uri))
+ fz_append_printf(ctx, buf, " href=\"%s\">\n", link->uri);
+ else
+ {
+ int linkNumber = fz_resolve_link(ctx, doc, link->uri, NULL, NULL);
+ fz_append_printf(ctx, buf, " href=\"#page%d\">\n", linkNumber+1);
+ }
+ }
+ }
+ fz_append_byte(ctx, buf, 0);
+ fz_drop_link(ctx, links);
+ }
+ fz_buffer_extract(ctx, buf, &data);
+ fz_drop_buffer(ctx, buf);
+
+ return (char*)data;
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *documentTitle(fz_document *doc)
+{
+ static char buf[100];
+ if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0)
+ return buf;
+ return "Untitled";
+}
+
+EMSCRIPTEN_KEEPALIVE
+fz_outline *loadOutline(fz_document *doc)
+{
+ return fz_load_outline(ctx, doc);
+}
+
+EMSCRIPTEN_KEEPALIVE
+void freeOutline(fz_outline *outline)
+{
+ fz_drop_outline(ctx, outline);
+}
+
+EMSCRIPTEN_KEEPALIVE
+char *outlineTitle(fz_outline *node)
+{
+ return node->title;
+}
+
+EMSCRIPTEN_KEEPALIVE
+int outlinePage(fz_outline *node)
+{
+ return node->page + 1;
+}
+
+EMSCRIPTEN_KEEPALIVE
+fz_outline *outlineDown(fz_outline *node)
+{
+ return node->down;
+}
+
+EMSCRIPTEN_KEEPALIVE
+fz_outline *outlineNext(fz_outline *node)
+{
+ return node->next;
+}
diff --git a/platform/wasm/wrap.js b/platform/wasm/wrap.js
new file mode 100644
index 00000000..ef5df57b
--- /dev/null
+++ b/platform/wasm/wrap.js
@@ -0,0 +1,51 @@
+var mupdf = {};
+
+Module.noExitRuntime = true;
+Module.noInitialRun = true;
+
+Module.onRuntimeInitialized = function () {
+ Module.ccall('initContext');
+ mupdf.openDocument = Module.cwrap('openDocument', 'number', ['string']);
+ mupdf.freeDocument = Module.cwrap('freeDocument', 'null', ['number']);
+ mupdf.documentTitle = Module.cwrap('documentTitle', 'string', ['number']);
+ mupdf.countPages = Module.cwrap('countPages', 'number', ['number']);
+ mupdf.pageWidth = Module.cwrap('pageWidth', 'number', ['number', 'number', 'number']);
+ mupdf.pageHeight = Module.cwrap('pageHeight', 'number', ['number', 'number', 'number']);
+ mupdf.pageLinks = Module.cwrap('pageLinks', 'string', ['number', 'number', 'number']);
+ mupdf.drawPageAsPNG = Module.cwrap('drawPageAsPNG', 'string', ['number', 'number', 'number']);
+ mupdf.drawPageAsHTML = Module.cwrap('drawPageAsHTML', 'string', ['number', 'number']);
+ mupdf.drawPageAsSVG = Module.cwrap('drawPageAsSVG', 'string', ['number', 'number']);
+ mupdf.loadOutline = Module.cwrap('loadOutline', 'number', ['number']);
+ mupdf.freeOutline = Module.cwrap('freeOutline', null, ['number']);
+ mupdf.outlineTitle = Module.cwrap('outlineTitle', 'string', ['number']);
+ mupdf.outlinePage = Module.cwrap('outlinePage', 'number', ['number']);
+ mupdf.outlineDown = Module.cwrap('outlineDown', 'number', ['number']);
+ mupdf.outlineNext = Module.cwrap('outlineNext', 'number', ['number']);
+};
+
+mupdf.documentOutline = function (doc) {
+ function makeOutline(node) {
+ var ul = document.createElement('ul');
+ while (node) {
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+ a.href = '#page' + mupdf.outlinePage(node);
+ a.textContent = mupdf.outlineTitle(node);
+ li.appendChild(a);
+ var down = mupdf.outlineDown(node);
+ if (down) {
+ li.appendChild(makeOutline(down));
+ }
+ ul.appendChild(li);
+ node = mupdf.outlineNext(node);
+ }
+ return ul;
+ }
+ var root = mupdf.loadOutline(doc);
+ if (root) {
+ var ul = makeOutline(root);
+ mupdf.freeOutline(root);
+ return ul;
+ }
+ return null;
+}