// Copyright (C) 2004-2022 Artifex Software, Inc. // // This file is part of MuPDF. // // MuPDF is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more // details. // // You should have received a copy of the GNU Affero General Public License // along with MuPDF. If not, see // // Alternative licensing terms are available from the licensor. // For commercial licensing, see or contact // Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, // CA 94945, U.S.A., +1(415)492-9861, for further information. /* global mupdf */ "use strict" // Import the WASM module. globalThis.__filename = "lib/mupdf-wasm.js" importScripts("lib/mupdf-wasm.js") // Import the MuPDF bindings. importScripts("lib/mupdf.js") const workerMethods = {} onmessage = async function (event) { let [ func, id, args ] = event.data try { let result = workerMethods[func](...args) postMessage([ "RESULT", id, result ]) } catch (error) { if (error instanceof mupdf.TryLaterError) { trylaterQueue.push(event) } else { postMessage([ "ERROR", id, { name: error.name, message: error.message, stack: error.stack } ]) } } } let trylaterScheduled = false let trylaterQueue = [] mupdf.onFetchCompleted = function (_id) { if (!trylaterScheduled) { trylaterScheduled = true setTimeout(() => { trylaterScheduled = false let currentQueue = trylaterQueue trylaterQueue = [] currentQueue.forEach(onmessage) }, 0) } } let openStream = null let openDocument = null workerMethods.setLogFilters = function (filters) { logFilters = filters } workerMethods.openStreamFromUrl = function (url, contentLength, progressive, prefetch) { openStream = new mupdf.Stream(url, contentLength, Math.max(progressive << 10, 1 << 16), prefetch) } workerMethods.openDocumentFromBuffer = function (buffer, magic) { openDocument = mupdf.Document.openDocument(buffer, magic) } workerMethods.openDocumentFromStream = function (magic) { if (openStream == null) { throw new Error("openDocumentFromStream called but no stream has been open") } openDocument = mupdf.Document.openDocument(openStream, magic) } workerMethods.freeDocument = function () { openDocument?.destroy() openDocument = null } workerMethods.documentTitle = function () { return openDocument.getMetaData(Document.META_INFO_TITLE) } workerMethods.documentOutline = function () { return openDocument.loadOutline() } workerMethods.countPages = function () { return openDocument.countPages() } workerMethods.layout = function (w, h, em) { return openDocument.layout(w, h, em) } workerMethods.userCSS = function (css) { return openDocument.userCSS(css) } // TODO - use hungarian notation for coord spaces // TODO - document the "- 1" better // TODO - keep page loaded? workerMethods.getPageSize = function (pageNumber) { let page = openDocument.loadPage(pageNumber - 1) let bounds = page.getBounds() return { width: bounds[2] - bounds[0], height: bounds[3] - bounds[1] } } workerMethods.getPageLinks = function (pageNumber) { let page = openDocument.loadPage(pageNumber - 1) let links = page.getLinks() return links.map((link) => { const [ x0, y0, x1, y1 ] = link.getBounds() let href if (link.isExternal()) { href = link.getURI() } else { const linkPageNumber = openDocument.resolveLink(link) // TODO - move to front-end // TODO - document the "+ 1" better href = `#page${linkPageNumber + 1}` } return { x: x0, y: y0, w: x1 - x0, h: y1 - y0, href, } }) } workerMethods.getPageText = function (pageNumber) { let page = openDocument.loadPage(pageNumber - 1) let text = page.toStructuredText(1).asJSON() return JSON.parse(text) } workerMethods.search = function (pageNumber, needle) { let page = openDocument.loadPage(pageNumber - 1) const hits = page.search(needle) let result = [] for (let hit of hits) { for (let quad of hit) { const [ ulx, uly, urx, ury, llx, lly, lrx, lry ] = quad result.push({ x: ulx, y: uly, w: urx - ulx, h: lly - uly, }) } } return result } workerMethods.getPageAnnotations = function (pageNumber, dpi) { let page = openDocument.loadPage(pageNumber - 1) if (page == null) { return [] } const annotations = page.getAnnotations() const doc_to_screen = [ dpi = 72, 0, 0, dpi / 72, 0, 0 ] return annotations.map((annotation) => { const [ x0, y0, x1, y1 ] = Matrix.transformRect(annotation.getBounds()) return { x: x0, y: y0, w: x1 - x0, h: y1 - y0, type: annotation.getType(), ref: annotation.pointer, } }) } // TODO - Move this to mupdf-view const lastPageRender = new Map() workerMethods.drawPageAsPNG = function (pageNumber, dpi) { const doc_to_screen = mupdf.Matrix.scale(dpi / 72, dpi / 72) // TODO - use canvas? let page = openDocument.loadPage(pageNumber - 1) let pixmap = page.toPixmap(doc_to_screen, mupdf.DeviceRGB, false) let png = pixmap?.saveAsPNG() pixmap?.destroy() return png } workerMethods.drawPageAsPixmap = function (pageNumber, dpi) { const doc_to_screen = mupdf.Matrix.scale(dpi / 72, dpi / 72) let page = openDocument.loadPage(pageNumber - 1) let bbox = Rect.transform(page.getBounds(), doc_to_screen) let pixmap = new mupdf.Pixmap(mupdf.ColorSpace.DeviceRGB, bbox, true) pixmap.clear(255) let device = new mupdf.DrawDevice(doc_to_screen, pixmap) page.run(device, Matrix.identity) device.close() let pixArray = pixmap.getPixels() let pixW = pixmap.getWidth() let pixH = pixmap.getHeight() let imageData = new ImageData(pixArray.slice(), pixW, pixH) pixmap.destroy() return imageData } postMessage([ "READY", Object.keys(workerMethods) ])