From 8ebce9c149112d59552ed530361f80372455fdb2 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Mon, 10 Sep 2018 18:18:49 +0200 Subject: Add Emscripten / WebAssembly build. Requires Linux (or possibly MacOS X) and an installed emsdk to build. --- platform/wasm/Makefile | 2 + platform/wasm/build.sh | 28 +++++ platform/wasm/readme.html | 135 ++++++++++++++++++++++ platform/wasm/view-as-html.html | 45 ++++++++ platform/wasm/view-as-svg.html | 44 +++++++ platform/wasm/view-page.html | 74 ++++++++++++ platform/wasm/view.html | 84 ++++++++++++++ platform/wasm/wrap.c | 247 ++++++++++++++++++++++++++++++++++++++++ platform/wasm/wrap.js | 51 +++++++++ 9 files changed, 710 insertions(+) create mode 100644 platform/wasm/Makefile create mode 100644 platform/wasm/build.sh create mode 100644 platform/wasm/readme.html create mode 100644 platform/wasm/view-as-html.html create mode 100644 platform/wasm/view-as-svg.html create mode 100644 platform/wasm/view-page.html create mode 100644 platform/wasm/view.html create mode 100644 platform/wasm/wrap.c create mode 100644 platform/wasm/wrap.js (limited to 'platform') 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 @@ + + + +MuPDF / WebAssembly + + + + + +

MuPDF WebAssembly

+ +

Building

+ +

+The WebAssembly build has only been tested on Linux at the moment. +If you use any other platform, you are on your own. + +

+In order to build this you will need to install the +Emscripten SDK +in /opt/emsdk. +If you install it elsewhere, you will need to edit the platform/wasm/build.sh +script to point to the appropriate location. + +

+From the MuPDF project, you can run make wasm to build the WebAssembly +library. The results of the build are a libmupdf.wasm binary and +libmupdf.js script, placed in platform/wasm/. + +

+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. + +

+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. + +

Using

+ +

+The example script in platform/wasm/view.html shows how to use the +MuPDF WebAssembly library. + +

+The first part is including the libmupdf.js script, which pulls in +and instantiates the WebAssembly module: + +

+<script src="libmupdf.js"></script>
+
+ +

+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: + +

+<script>
+Module.preRun = function () {
+	FS.createPreloadedFile(".", "input.pdf", "input.pdf", true, false);
+}
+</script>
+
+ +

+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. + +

+
mupdf.openDocument(filename) +
Open a document and return a handle. +
mupdf.freeDocument(doc) +
Free a document and its associated resources. +
mupdf.documentTitle(doc) +
Return the document title as a string. +
mupdf.documentOutline(doc) +
Return a DOM element containing the table of contents formatted as +an unordered HTML list with links to pages using anchor fragments "#page%d". +
mupdf.countPages(doc) +
Return the number of pages in the document. +
mupdf.pageWidth(doc, page, dpi) and mupdf.pageHeight(doc, page, dpi) +
Return the dimensions of a page. Page numbering starts at 1. +
mupdf.drawPageAsPNG(doc, page, dpi) +
Render the page and return a PNG image formatted as a data URI, +suitable for using as an image source attribute. +
mupdf.pageLinks(doc, page, dpi) +
Retrieve an HTML string describing the links on a page, +suitable for including as the innerHTML of an image map. +
mupdf.drawPageAsSVG(doc, page) +
Return a string with the contents of the page in SVG format. +
mupdf.drawPageAsHTML(doc, page) +
Return a string with the contents of the page in HTML format, +using absolute positioned elements. +
+ +

Example

+ +

+Here is a very simple example of loading a document and drawing its first page: + +

+<!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>
+
+ + + 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 @@ + + + +Loading... + + + + + + +
+ + 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 @@ + + + +Loading... + + + + + + +
+ + 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 @@ + + + +Loading... + + + + + + +

+ + + + + +

+ + + 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 @@ + + + +Loading... + + + + + +

+ 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, "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; +} -- cgit v1.2.3