diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..fa2cf816e --- /dev/null +++ b/Makefile @@ -0,0 +1,160 @@ +REPO = git@github.com:andreasgal/pdf.js.git +BUILD_DIR := build +DEFAULT_BROWSERS := test/resources/browser_manifests/browser_manifest.json +DEFAULT_TESTS := test/test_manifest.json + +# JS files needed for pdf.js. +# This list doesn't account for the 'worker' directory. +PDF_JS_FILES = \ + pdf.js \ + crypto.js \ + fonts.js \ + glyphlist.js \ + $(NULL) + +# not sure what to do for all yet +all: help + +# make server +# +# This target starts a local web server at localhost:8888. This can be +# used for testing all browsers. +server: + @cd test; python test.py --port=8888; + +test: shell-test browser-test + +# make browser-test +# +# This target runs in-browser tests using two primary arguments: a +# test manifest file, and a browser manifest file. Both are simple +# JSON formats, and examples can be found in the test/ directory. The +# target will inspect the environment for the PDF_TESTS and +# PDF_BROWSERS variables, and use those if found. Otherwise, the +# defaults at the top of this file are used. +ifeq ($(PDF_TESTS),) +PDF_TESTS := $(DEFAULT_TESTS) +endif +ifeq ($(PDF_BROWSERS),) +PDF_BROWSERS := $(DEFAULT_BROWSERS) +endif + +browser-test: + @if [ ! "$(PDF_BROWSERS)" ]; then \ + echo "Browser manifest file $(PDF_BROWSERS) does not exist."; \ + echo "Try copying one of the examples" \ + "in test/resources/browser_manifests/"; \ + exit 1; \ + fi; + + cd test; \ + python test.py --reftest \ + --browserManifestFile=$(abspath $(PDF_BROWSERS)) \ + --manifestFile=$(abspath $(PDF_TESTS)) + +# make shell-test +# +# This target runs all of the tests that can be run in a JS shell. +# The shell used is taken from the JS_SHELL environment variable. If +# that veriable is not defined, the script will attempt to use the copy +# of Rhino that comes with the Closure compiler used for producing the +# website. +SHELL_TARGET = $(NULL) +ifeq ($(JS_SHELL),) +JS_SHELL := "java -cp $(BUILD_DIR)/compiler.jar" +JS_SHELL += "com.google.javascript.jscomp.mozilla.rhino.tools.shell.Main" +SHELL_TARGET = compiler +endif + +shell-test: shell-msg $(SHELL_TARGET) font-test +shell-msg: +ifeq ($(SHELL_TARGET), compiler) + @echo "No JS_SHELL env variable present." + @echo "The default is to find a copy of Rhino and try that." +endif + @echo "JS shell command is: $(JS_SHELL)" + +font-test: + @echo "font test stub." + +# make lint +# +# This target runs the Closure Linter on most of our JS files. +# To install gjslint, see: +# +# +SRC_DIRS := . utils worker web +GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) +lint: + gjslint $(GJSLINT_FILES) + +# make web +# +# This target produces the website for the project, by checking out +# the gh-pages branch underneath the build directory, and then move +# the various viewer files into place. +# +# TODO: Use the Closure compiler to optimize the pdf.js files. +# +GH_PAGES = $(BUILD_DIR)/gh-pages +web: | compiler pages-repo \ + $(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \ + $(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \ + $(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) + + @cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html; + @cd $(GH_PAGES); git add -A; + @echo "Website built in $(GH_PAGES)." + +# make pages-repo +# +# This target clones the gh-pages repo into the build directory. It +# deletes the current contents of the repo, since we overwrite +# everything with data from the master repo. The 'make web' target +# then uses 'git add -A' to track additions, modifications, moves, +# and deletions. +pages-repo: | $(BUILD_DIR) + @if [ ! -d "$(GH_PAGES)" ]; then \ + git clone -b gh-pages $(REPO) $(GH_PAGES); \ + rm -rf $(GH_PAGES)/*; \ + fi; + @mkdir -p $(GH_PAGES)/web; + @mkdir -p $(GH_PAGES)/web/images; + +$(GH_PAGES)/%.js: %.js + @cp $< $@ + +$(GH_PAGES)/web/%: web/% + @cp $< $@ + +$(GH_PAGES)/web/images/%: web/images/% + @cp $< $@ + +# make compiler +# +# This target downloads the Closure compiler, and places it in the +# build directory. This target is also useful when the user doesn't +# have a JS shell available--we can have them use the Rhino shell that +# comes with Closure. +COMPILER_URL = http://closure-compiler.googlecode.com/files/compiler-latest.zip + +compiler: $(BUILD_DIR)/compiler.zip +$(BUILD_DIR)/compiler.zip: | $(BUILD_DIR) + curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip; + cd $(BUILD_DIR); unzip compiler.zip compiler.jar; + +# Make sure there's a build directory. +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +clean: + rm -rf $(BUILD_DIR) + +# make help +# +# This target just prints out a message to read these comments. :) +help: + @echo "Read the comments in the Makefile for guidance."; + +.PHONY: all test browser-test font-test shell-test \ + shell-msg lint clean web compiler help server diff --git a/crypto.js b/crypto.js index e888d0212..d73408ad6 100644 --- a/crypto.js +++ b/crypto.js @@ -139,9 +139,9 @@ var CipherTransform = (function() { }, decryptString: function(s) { var cipher = new this.stringCipherConstructor(); - var data = string2bytes(s); + var data = stringToBytes(s); data = cipher.encryptBlock(data); - return bytes2string(data); + return bytesToString(data); } }; return constructor; diff --git a/fonts.js b/fonts.js index 12d7ce82e..8701f8e5f 100644 --- a/fonts.js +++ b/fonts.js @@ -70,11 +70,14 @@ var Fonts = (function Fonts() { return fonts[fontName]; }, setActive: function fonts_setActive(fontName, size) { - current = fonts[fontName]; - charsCache = current.charsCache; - var sizes = current.sizes; - if (!(measureCache = sizes[size])) - measureCache = sizes[size] = Object.create(null); + // |current| can be null is fontName is a built-in font + // (e.g. "sans-serif") + if ((current = fonts[fontName])) { + charsCache = current.charsCache; + var sizes = current.sizes; + if (!(measureCache = sizes[size])) + measureCache = sizes[size] = Object.create(null); + } ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"'; }, charsToUnicode: function fonts_chars2Unicode(chars) { @@ -87,7 +90,7 @@ var Fonts = (function Fonts() { return str; // translate the string using the font's encoding - var encoding = current.properties.encoding; + var encoding = current ? current.properties.encoding : null; if (!encoding) return chars; @@ -218,12 +221,7 @@ var FontLoader = { window.addEventListener( "message", function(e) { - var fontNames = e.data; - // Firefox 5 doesn't parse the JSON here. Welcome to the - // Wonderful Web World. - if ("string" == typeof(fontNames)) { - fontNames = fontNames.split(","); - } + var fontNames = JSON.parse(e.data); for (var i = 0; i < fontNames.length; ++i) { var font = Fonts.lookup(fontNames[i]); font.loading = false; @@ -251,7 +249,7 @@ var FontLoader = { } src += ' var fontNames=['+ fontNamesArray +'];\n'; src += ' window.onload = function () {\n' - src += ' top.postMessage(fontNames, "*");\n'; + src += ' top.postMessage(JSON.stringify(fontNames), "*");\n'; src += ' }'; src += ''; for (var i = 0; i < names.length; ++i) { @@ -1812,8 +1810,8 @@ CFF.prototype = { var data = "\x8b\x14" + // defaultWidth "\x8b\x15" + // nominalWidth - self.encodeNumber(properties.stdHW) + "\x0a" + // StdHW - self.encodeNumber(properties.stdVW) + "\x0b"; // StdVW + self.encodeNumber(properties.stdHW || 0) + "\x0a" + // StdHW + self.encodeNumber(properties.stdVW || 0) + "\x0b"; // StdVW var stemH = properties.stemSnapH; for (var i = 0; i < stemH.length; i++) diff --git a/pdf.js b/pdf.js index 814c99f75..a9f0ee935 100644 --- a/pdf.js +++ b/pdf.js @@ -2842,7 +2842,7 @@ var Page = (function() { constructor.prototype = { getPageProp: function(key) { - return this.pageDict.get(key); + return this.xref.fetchIfRef(this.pageDict.get(key)); }, inheritPageProp: function(key) { var dict = this.pageDict; @@ -2971,6 +2971,7 @@ var Catalog = (function() { var PDFDoc = (function() { function constructor(stream) { + assertWellFormed(stream.length > 0, "stream must have data"); this.stream = stream; this.setup(); } @@ -3565,6 +3566,7 @@ var CanvasGraphics = (function() { }, execute: function(code, xref, resources) { + resources = xref.fetchIfRef(resources) || new Dict(); var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; this.xref = xref; this.res = resources || new Dict(); @@ -3578,6 +3580,7 @@ var CanvasGraphics = (function() { }, compile: function(stream, xref, resources, fonts) { + resources = xref.fetchIfRef(resources) || new Dict(); var xobjs = xref.fetchIfRef(resources.get("XObject")) || new Dict(); var parser = new Parser(new Lexer(stream), false); @@ -3874,7 +3877,7 @@ var CanvasGraphics = (function() { this.ctx.translate(this.current.x, -1 * this.current.y); var font = Fonts.lookup(this.current.fontName); - if (font) + if (font && font.properties.textMatrix) this.ctx.transform.apply(this.ctx, font.properties.textMatrix); this.ctx.fillText(text, 0, 0); @@ -4451,7 +4454,7 @@ var ColorSpace = (function() { break; case "Indexed": var base = ColorSpace.parse(cs[1], xref, res); - var hiVal = cs[2]; + var hiVal = cs[2] + 1; var lookup = xref.fetchIfRef(cs[3]); return new IndexedCS(base, hiVal, lookup); case "Lab": diff --git a/test/driver.js b/test/driver.js new file mode 100644 index 000000000..b4c2c64e0 --- /dev/null +++ b/test/driver.js @@ -0,0 +1,233 @@ +/* + * A Test Driver for PDF.js + */ + + +var appPath, browser, canvas, currentTaskIdx, manifest, stdout; + +function queryParams() { + var qs = window.location.search.substring(1); + var kvs = qs.split("&"); + var params = { }; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split("="); + params[unescape(kv[0])] = unescape(kv[1]); + } + return params; +} + +function load() { + var params = queryParams(); + browser = params.browser; + manifestFile = params.manifestFile; + appPath = params.path; + + canvas = document.createElement("canvas"); + canvas.mozOpaque = true; + stdout = document.getElementById("stdout"); + + log("load...\n"); + + log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n"); + log("Fetching manifest "+ manifestFile +"..."); + + var r = new XMLHttpRequest(); + r.open("GET", manifestFile, false); + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + log("done\n"); + manifest = JSON.parse(r.responseText); + currentTaskIdx = 0, nextTask(); + } + }; + r.send(null); +} +window.onload = load; + +function nextTask() { + if (currentTaskIdx == manifest.length) { + return done(); + } + var task = manifest[currentTaskIdx]; + task.round = 0; + + log("Loading file "+ task.file +"\n"); + + var r = new XMLHttpRequest(); + r.open("GET", task.file); + r.mozResponseType = r.responseType = "arraybuffer"; + r.onreadystatechange = function() { + var failure; + if (r.readyState == 4) { + var data = r.mozResponseArrayBuffer || r.mozResponse || + r.responseArrayBuffer || r.response; + + try { + task.pdfDoc = new PDFDoc(new Stream(data)); + } catch(e) { + failure = 'load PDF doc: '+ e.toString(); + } + + task.pageNum = 1, nextPage(task, failure); + } + }; + r.send(null); +} + +function isLastPage(task) { + return (task.pdfDoc && (task.pageNum > task.pdfDoc.numPages)); +} + +function nextPage(task, loadError) { + if (isLastPage(task)) { + if (++task.round < task.rounds) { + log(" Round "+ (1 + task.round) +"\n"); + task.pageNum = 1; + } else { + ++currentTaskIdx, nextTask(); + return; + } + } + + var failure = loadError || ''; + + var ctx = null; + var fonts; + var gfx = null; + var page = null; + + if (!failure) { + log(" loading page "+ task.pageNum +"... "); + ctx = canvas.getContext("2d"); + fonts = []; + try { + gfx = new CanvasGraphics(ctx); + page = task.pdfDoc.getPage(task.pageNum); + page.compile(gfx, fonts); + } catch(e) { + failure = 'compile: '+ e.toString(); + } + } + + if (!failure) { + try { + var pdfToCssUnitsCoef = 96.0 / 72.0; + // using mediaBox for the canvas size + var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); + var pageHeight = (page.mediaBox[3] - page.mediaBox[1]); + canvas.width = pageWidth * pdfToCssUnitsCoef; + canvas.height = pageHeight * pdfToCssUnitsCoef; + clear(ctx); + } catch(e) { + failure = 'page setup: '+ e.toString(); + } + } + + if (!failure) { + try { + FontLoader.bind(fonts, function() { + snapshotCurrentPage(gfx, page, task, failure); + }); + } catch(e) { + failure = 'fonts: '+ e.toString(); + } + } + + if (failure) { + // Skip right to snapshotting if there was a failure, since the + // fonts might be in an inconsistent state. + snapshotCurrentPage(gfx, page, task, failure); + } +} + +function snapshotCurrentPage(gfx, page, task, failure) { + log("done, snapshotting... "); + + if (!failure) { + try { + page.display(gfx); + } catch(e) { + failure = 'render: '+ e.toString(); + } + } + + sendTaskResult(canvas.toDataURL("image/png"), task, failure); + log("done"+ (failure ? " (failed!: "+ failure +")" : "") +"\n"); + + // Set up the next request + backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; + setTimeout(function() { + ++task.pageNum, nextPage(task); + }, + backoff + ); +} + +function sendQuitRequest() { + var r = new XMLHttpRequest(); + r.open("POST", "/tellMeToQuit?path=" + escape(appPath), false); + r.send(""); +} + +function quitApp() { + log("Done!"); + document.body.innerHTML = "Tests are finished.

CLOSE ME!

"; + if (window.SpecialPowers) { + SpecialPowers.quitApplication(); + } else { + sendQuitRequest(); + window.close(); + } +} + +function done() { + if (inFlightRequests > 0) { + document.getElementById("inFlightCount").innerHTML = inFlightRequests; + setTimeout(done, 100); + } else { + setTimeout(quitApp, 100); + } +} + +var inFlightRequests = 0; +function sendTaskResult(snapshot, task, failure) { + var result = { browser: browser, + id: task.id, + numPages: task.pdfDoc.numPages, + failure: failure, + file: task.file, + round: task.round, + page: task.pageNum, + snapshot: snapshot }; + + var r = new XMLHttpRequest(); + // (The POST URI is ignored atm.) + r.open("POST", "/submit_task_results", true); + r.setRequestHeader("Content-Type", "application/json"); + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + inFlightRequests--; + } + } + document.getElementById("inFlightCount").innerHTML = inFlightRequests++; + r.send(JSON.stringify(result)); +} + +function clear(ctx) { + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); +} + +/* Auto-scroll if the scrollbar is near the bottom, otherwise do nothing. */ +function checkScrolling() { + if ((stdout.scrollHeight - stdout.scrollTop) <= stdout.offsetHeight) { + stdout.scrollTop = stdout.scrollHeight; + } +} + +function log(str) { + stdout.innerHTML += str; + checkScrolling(); +} \ No newline at end of file diff --git a/test/pdfs/shavian.pdf.link b/test/pdfs/shavian.pdf.link new file mode 100644 index 000000000..42c438644 --- /dev/null +++ b/test/pdfs/shavian.pdf.link @@ -0,0 +1 @@ +http://www.unicode.org/charts/PDF/U10450.pdf \ No newline at end of file diff --git a/test/test.py b/test/test.py index 52e91476a..3f007a21a 100644 --- a/test/test.py +++ b/test/test.py @@ -17,7 +17,6 @@ TMPDIR = 'tmp' VERBOSE = False SERVER_HOST = "localhost" -SERVER_PORT = 8080 class TestOptions(OptionParser): def __init__(self, **kwargs): @@ -34,6 +33,8 @@ class TestOptions(OptionParser): self.add_option("--reftest", action="store_true", dest="reftest", help="Automatically start reftest showing comparison test failures, if there are any.", default=False) + self.add_option("--port", action="store", dest="port", type="int", + help="The port the HTTP server should listen on.", default=8080) self.set_usage(USAGE_EXAMPLE) def verifyOptions(self, options): @@ -44,7 +45,7 @@ class TestOptions(OptionParser): if options.browser and options.browserManifestFile: print "Warning: ignoring browser argument since manifest file was also supplied" if not options.browser and not options.browserManifestFile: - print "No browser arguments supplied, so just starting server on port %s." % SERVER_PORT + print "Starting server on port %s." % options.port return options def prompt(question): @@ -99,7 +100,7 @@ class PDFTestHandler(BaseHTTPRequestHandler): self.send_header("Content-Type", MIMEs[ext]) self.send_header("Content-Length", os.path.getsize(path)) self.end_headers() - with open(path) as f: + with open(path, "rb") as f: self.wfile.write(f.read()) def do_GET(self): @@ -275,7 +276,7 @@ def downloadLinkedPDFs(manifestList): sys.stdout.flush() response = urllib2.urlopen(link) - with open(f, 'w') as out: + with open(f, 'wb') as out: out.write(response.read()) print 'done' @@ -325,7 +326,7 @@ def startBrowsers(browsers, options): for b in browsers: b.setup() print 'Launching', b.name - host = 'http://%s:%s' % (SERVER_HOST, SERVER_PORT) + host = 'http://%s:%s' % (SERVER_HOST, options.port) path = '/test/test_slave.html?' qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) qs += '&path=' + b.path @@ -482,8 +483,8 @@ def maybeUpdateRefImages(options, browser): print 'done' -def startReftest(browser): - url = "http://%s:%s" % (SERVER_HOST, SERVER_PORT) +def startReftest(browser, options): + url = "http://%s:%s" % (SERVER_HOST, options.port) url += "/test/resources/reftest-analyzer.xhtml" url += "#web=/test/eq.log" try: @@ -511,7 +512,7 @@ def runTests(options, browsers): maybeUpdateRefImages(options, browsers[0]) elif options.reftest and State.numEqFailures > 0: print "\nStarting reftest harness to examine %d eq test failures." % State.numEqFailures - startReftest(browsers[0]) + startReftest(browsers[0], options) def main(): optionParser = TestOptions() @@ -520,7 +521,7 @@ def main(): if options == None: sys.exit(1) - httpd = TestServer((SERVER_HOST, SERVER_PORT), PDFTestHandler) + httpd = TestServer((SERVER_HOST, options.port), PDFTestHandler) httpd_thread = threading.Thread(target=httpd.serve_forever) httpd_thread.setDaemon(True) httpd_thread.start() @@ -531,8 +532,11 @@ def main(): else: # just run the server print "Running HTTP server. Press Ctrl-C to quit." - while True: - time.sleep(1) + try: + while True: + time.sleep(1) + except (KeyboardInterrupt): + print "\nExiting." if __name__ == '__main__': main() diff --git a/test/test_manifest.json b/test/test_manifest.json index 06787925f..4302e1f6e 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -26,6 +26,12 @@ "rounds": 1, "type": "load" }, + { "id": "shavian-load", + "file": "pdfs/shavian.pdf", + "link": true, + "rounds": 1, + "type": "load" + }, { "id": "sizes", "file": "pdfs/sizes.pdf", "rounds": 1, diff --git a/test/test_slave.html b/test/test_slave.html index 3180418fa..f6d1f7f48 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -6,222 +6,7 @@ - + diff --git a/compressed.tracemonkey-pldi-09.pdf b/web/compressed.tracemonkey-pldi-09.pdf similarity index 100% rename from compressed.tracemonkey-pldi-09.pdf rename to web/compressed.tracemonkey-pldi-09.pdf diff --git a/images/buttons.png b/web/images/buttons.png similarity index 100% rename from images/buttons.png rename to web/images/buttons.png diff --git a/images/source/Buttons.psd.zip b/web/images/source/Buttons.psd.zip similarity index 100% rename from images/source/Buttons.psd.zip rename to web/images/source/Buttons.psd.zip diff --git a/images/source/FileButton.psd.zip b/web/images/source/FileButton.psd.zip similarity index 100% rename from images/source/FileButton.psd.zip rename to web/images/source/FileButton.psd.zip diff --git a/web/index.html.template b/web/index.html.template new file mode 100644 index 000000000..c3086f078 --- /dev/null +++ b/web/index.html.template @@ -0,0 +1,88 @@ + + + + + + andreasgal/pdf.js @ GitHub + + + + + + Fork me on GitHub + +
+ +
+ + + + +
+ +

pdf.js + by andreasgal

+ +
+ PDF Reader in JavaScript +
+ +

Try it out!

+

Live demo lives here.

+ +

Authors

+

Vivien Nicolas (21@vingtetun.org) +
Andreas Gal (andreas.gal@gmail.com) +
Soumya Deb (debloper@gmail.com) +
Chris Jones (jones.chris.g@gmail.com) +
Justin D'Arcangelo (justindarc@gmail.com) +
sbarman (sbarman@eecs.berkeley.edu) +
+

+

Contact

+

(andreas.gal@gmail.com) +

+ + +

Download

+

+ You can download this project in either + zip or + tar formats. +

+

You can also clone the project with Git + by running: +

$ git clone git://github.com/andreasgal/pdf.js
+

+ + + +
+ + + + diff --git a/multi_page_viewer.css b/web/multi_page_viewer.css similarity index 100% rename from multi_page_viewer.css rename to web/multi_page_viewer.css diff --git a/multi_page_viewer.html b/web/multi_page_viewer.html similarity index 86% rename from multi_page_viewer.html rename to web/multi_page_viewer.html index df71d6690..841d2dba9 100644 --- a/multi_page_viewer.html +++ b/web/multi_page_viewer.html @@ -4,10 +4,10 @@ pdf.js Multi-Page Viewer - - - - + + + + diff --git a/multi_page_viewer.js b/web/multi_page_viewer.js similarity index 100% rename from multi_page_viewer.js rename to web/multi_page_viewer.js diff --git a/viewer.css b/web/viewer.css similarity index 100% rename from viewer.css rename to web/viewer.css diff --git a/viewer.html b/web/viewer.html similarity index 74% rename from viewer.html rename to web/viewer.html index e73357d43..feb1bb00f 100644 --- a/viewer.html +++ b/web/viewer.html @@ -6,11 +6,10 @@ - - - - - + + + + diff --git a/viewer.js b/web/viewer.js similarity index 94% rename from viewer.js rename to web/viewer.js index 4071151aa..6702a6735 100644 --- a/viewer.js +++ b/web/viewer.js @@ -91,7 +91,9 @@ function displayPage(num) { infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms"; } - FontLoader.bind(fonts, displayPage); + // Always defer call to displayPage() to work around bug in + // Firefox error reporting from XHR callbacks. + FontLoader.bind(fonts, function () { setTimeout(displayPage, 0); }); } function nextPage() { diff --git a/viewer_worker.html b/web/viewer_worker.html similarity index 82% rename from viewer_worker.html rename to web/viewer_worker.html index 89fb8a087..21a5be3ca 100644 --- a/viewer_worker.html +++ b/web/viewer_worker.html @@ -1,10 +1,10 @@ Simple pdf.js page worker viewer - - - - + + + +