diff options
Diffstat (limited to 'platform/wasm')
-rw-r--r-- | platform/wasm/Makefile | 2 | ||||
-rw-r--r-- | platform/wasm/build.sh | 28 | ||||
-rw-r--r-- | platform/wasm/readme.html | 135 | ||||
-rw-r--r-- | platform/wasm/view-as-html.html | 45 | ||||
-rw-r--r-- | platform/wasm/view-as-svg.html | 44 | ||||
-rw-r--r-- | platform/wasm/view-page.html | 74 | ||||
-rw-r--r-- | platform/wasm/view.html | 84 | ||||
-rw-r--r-- | platform/wasm/wrap.c | 247 | ||||
-rw-r--r-- | platform/wasm/wrap.js | 51 |
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> +<script src="libmupdf.js"></script> +</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> +<script> +Module.preRun = function () { + FS.createPreloadedFile(".", "input.pdf", "input.pdf", true, false); +} +</script> +</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> +<!DOCTYPE html> +<html> +<head> +<script src="libmupdf.js"></script> +<script> +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); +} +</script> +</head> +<body> +<img id="page1" usemap="#page1map"> +<map id="page1map" name="page1map"></map> +</body> +</html> +</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()"><</button> +<input id="pageNumber" onChange="gotoPage()" type="text" size="5"> +<button onClick="nextPage()">></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; +} |