From a27771ebaa40caa62bb6a6e54e8ee51434930e09 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Wed, 22 Jun 2011 16:56:31 -0700 Subject: [PATCH 01/45] Change test.py to use an external browser_manifest.json file, and use OptionParser to handle the command line. --- browser_manifest.json | 7 +++ test.py | 100 ++++++++++++++++++++++++++++++------------ 2 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 browser_manifest.json diff --git a/browser_manifest.json b/browser_manifest.json new file mode 100644 index 000000000..79115d1a4 --- /dev/null +++ b/browser_manifest.json @@ -0,0 +1,7 @@ +[ + { + "name":"Firefox 5", + "path":"/Applications/Firefox.app", + "type":"firefox" + } +] \ No newline at end of file diff --git a/test.py b/test.py index 0c326ec09..8314bd794 100644 --- a/test.py +++ b/test.py @@ -1,18 +1,41 @@ -import json, os, sys, subprocess, urllib2 +import json, platform, os, sys, subprocess, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from optparse import OptionParser from urlparse import urlparse -def prompt(question): - '''Return True iff the user answered "yes" to |question|.''' - inp = raw_input(question +' [yes/no] > ') - return inp == 'yes' +USAGE_EXAMPLE = "%prog" ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' +DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json' REFDIR = 'ref' TMPDIR = 'tmp' VERBOSE = False +class TestOptions(OptionParser): + def __init__(self, **kwargs): + OptionParser.__init__(self, **kwargs) + self.add_option("-m", "--masterMode", action="store_true", dest="masterMode", + help="Run the script in master mode.", default=False) + self.add_option("--manifestFile", action="store", type="string", dest="manifestFile", + help="A JSON file in the form of test_manifest.json (the default).") + self.add_option("--browserManifestFile", action="store", type="string", + dest="browserManifestFile", + help="A JSON file in the form of browser_manifest.json (the default).", + default=DEFAULT_BROWSER_MANIFEST_FILE) + self.set_usage(USAGE_EXAMPLE) + + def verifyOptions(self, options): + if options.masterMode and options.manifestFile: + self.error("--masterMode and --manifestFile must not be specified at the same time.") + options.manifestFile = DEFAULT_MANIFEST_FILE + return options + +def prompt(question): + '''Return True iff the user answered "yes" to |question|.''' + inp = raw_input(question +' [yes/no] > ') + return inp == 'yes' + MIMEs = { '.css': 'text/css', '.html': 'text/html', @@ -100,13 +123,34 @@ class PDFTestHandler(BaseHTTPRequestHandler): State.done = (0 == State.remaining) +# this just does Firefox for now +class BrowserCommand(): + def __init__(self, browserRecord): + print browserRecord + self.name = browserRecord["name"] + self.path = browserRecord["path"] + self.type = browserRecord["type"] + + if platform.system() == "Darwin" and self.path.endswith(".app"): + self._fixupMacPath() + + if not os.path.exists(self.path): + throw("Path to browser '%s' does not exist." % self.path) + + def _fixupMacPath(self): + self.path = self.path + "/Contents/MacOS/firefox-bin" -def setUp(manifestFile, masterMode): +def makeBrowserCommands(browserManifestFile): + with open(browserManifestFile) as bmf: + browsers = [BrowserCommand(browser) for browser in json.load(bmf)] + return browsers + +def setUp(options): # Only serve files from a pdf.js clone assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') - State.masterMode = masterMode - if masterMode and os.path.isdir(TMPDIR): + State.masterMode = options.masterMode + if options.masterMode and os.path.isdir(TMPDIR): print 'Temporary snapshot dir tmp/ is still around.' print 'tmp/ can be removed if it has nothing you need.' if prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'): @@ -114,14 +158,10 @@ def setUp(manifestFile, masterMode): assert not os.path.isdir(TMPDIR) - testBrowsers = [ b for b in - ( 'firefox5', ) -#'chrome12', 'chrome13', 'firefox4', 'firefox6','opera11' ): - if os.access(b, os.R_OK | os.X_OK) ] + testBrowsers = makeBrowserCommands(options.browserManifestFile) - mf = open(manifestFile) - manifestList = json.load(mf) - mf.close() + with open(options.manifestFile) as mf: + manifestList = json.load(mf) for item in manifestList: f, isLink = item['file'], item.get('link', False) @@ -140,22 +180,26 @@ def setUp(manifestFile, masterMode): print 'done' + print testBrowsers + for b in testBrowsers: - State.taskResults[b] = { } + State.taskResults[b.name] = { } for item in manifestList: id, rounds = item['id'], int(item['rounds']) State.manifest[id] = item taskResults = [ ] for r in xrange(rounds): taskResults.append([ ]) - State.taskResults[b][id] = taskResults + State.taskResults[b.name][id] = taskResults State.remaining = len(manifestList) + + for b in testBrowsers: - print 'Launching', b - qs = 'browser='+ b +'&manifestFile='+ manifestFile - subprocess.Popen(( os.path.abspath(os.path.realpath(b)), + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)), 'http://localhost:8080/test_slave.html?'+ qs)) @@ -285,14 +329,14 @@ def processResults(): print 'done' -def main(args): - masterMode = False - manifestFile = DEFAULT_MANIFEST_FILE - if len(args) == 1: - masterMode = (args[0] == '-m') - manifestFile = args[0] if not masterMode else manifestFile +def main(): + optionParser = TestOptions() + options, args = optionParser.parse_args() + options = optionParser.verifyOptions(options) + if options == None: + sys.exit(1) - setUp(manifestFile, masterMode) + setUp(options) server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) while not State.done: @@ -301,4 +345,4 @@ def main(args): processResults() if __name__ == '__main__': - main(sys.argv[1:]) + main() From aa21228448b549f6cc8ea4522cfb653c98592e01 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Wed, 22 Jun 2011 17:12:02 -0700 Subject: [PATCH 02/45] remove some accidental print statements. --- test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test.py b/test.py index 8314bd794..75810f43e 100644 --- a/test.py +++ b/test.py @@ -126,7 +126,6 @@ class PDFTestHandler(BaseHTTPRequestHandler): # this just does Firefox for now class BrowserCommand(): def __init__(self, browserRecord): - print browserRecord self.name = browserRecord["name"] self.path = browserRecord["path"] self.type = browserRecord["type"] @@ -180,8 +179,6 @@ def setUp(options): print 'done' - print testBrowsers - for b in testBrowsers: State.taskResults[b.name] = { } for item in manifestList: From 2ac8bbed51a5eace305842a62ebe2527751bfe6d Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 09:05:51 -0700 Subject: [PATCH 03/45] Add new test directories. --- test/.gitignore | 0 test/pdfs/.gitignore | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/.gitignore create mode 100644 test/pdfs/.gitignore diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore new file mode 100644 index 000000000..e69de29bb From 2454354b5907778bb4f3fd6db012ab8b8f2c44fc Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 09:10:06 -0700 Subject: [PATCH 04/45] Move some files around. --- browser_manifest.json => test/browser_manifest.json | 0 {tests => test/pdfs}/canvas.pdf | Bin {tests => test/pdfs}/pdf.pdf.link | 0 {tests => test/pdfs}/tracemonkey.pdf | Bin test.py => test/test.py | 0 test_manifest.json => test/test_manifest.json | 0 test_slave.html => test/test_slave.html | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename browser_manifest.json => test/browser_manifest.json (100%) rename {tests => test/pdfs}/canvas.pdf (100%) rename {tests => test/pdfs}/pdf.pdf.link (100%) rename {tests => test/pdfs}/tracemonkey.pdf (100%) rename test.py => test/test.py (100%) rename test_manifest.json => test/test_manifest.json (100%) rename test_slave.html => test/test_slave.html (100%) diff --git a/browser_manifest.json b/test/browser_manifest.json similarity index 100% rename from browser_manifest.json rename to test/browser_manifest.json diff --git a/tests/canvas.pdf b/test/pdfs/canvas.pdf similarity index 100% rename from tests/canvas.pdf rename to test/pdfs/canvas.pdf diff --git a/tests/pdf.pdf.link b/test/pdfs/pdf.pdf.link similarity index 100% rename from tests/pdf.pdf.link rename to test/pdfs/pdf.pdf.link diff --git a/tests/tracemonkey.pdf b/test/pdfs/tracemonkey.pdf similarity index 100% rename from tests/tracemonkey.pdf rename to test/pdfs/tracemonkey.pdf diff --git a/test.py b/test/test.py similarity index 100% rename from test.py rename to test/test.py diff --git a/test_manifest.json b/test/test_manifest.json similarity index 100% rename from test_manifest.json rename to test/test_manifest.json diff --git a/test_slave.html b/test/test_slave.html similarity index 100% rename from test_slave.html rename to test/test_slave.html From 18afc896f610253a20040067a3dc8c53eaed9f8d Mon Sep 17 00:00:00 2001 From: sbarman <sbarman@L3CWZ5T.(none)> Date: Thu, 23 Jun 2011 09:41:59 -0700 Subject: [PATCH 05/45] fix for uncompressed flatestream blocks --- pdf.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index d940c4f8b..326c31234 100644 --- a/pdf.js +++ b/pdf.js @@ -479,17 +479,17 @@ var FlateStream = (function() { array[i++] = what; } - var bytes = this.bytes; - var bytesPos = this.bytesPos; - // read block header var hdr = this.getBits(3); if (hdr & 1) this.eof = true; hdr >>= 1; - var b; if (hdr == 0) { // uncompressed block + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); var blockLen = b; @@ -502,18 +502,24 @@ var FlateStream = (function() { if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); check |= (b << 8); - if (check != (~this.blockLen & 0xffff)) + if (check != (~blockLen & 0xffff)) error("Bad uncompressed block length in flate stream"); + + this.codeBuf = 0; + this.codeSize = 0; + var bufferLength = this.bufferLength; var buffer = this.ensureBuffer(bufferLength + blockLen); - this.bufferLength = bufferLength + blockLen; - for (var n = bufferLength; n < blockLen; ++n) { + var end = bufferLength + blockLen; + this.bufferLength = end; + for (var n = bufferLength; n < end; ++n) { if (typeof (b = bytes[bytesPos++]) == "undefined") { this.eof = true; break; } buffer[n] = b; } + this.bytesPos = bytesPos; return; } From 27935325991d9d38ca47a1564f082cee35a9be16 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 09:48:34 -0700 Subject: [PATCH 06/45] Modify paths of web resources to work with test resources more buried. --- test/browser_manifest.json | 2 +- test/test.py | 18 +++++++++--------- test/test_manifest.json | 8 ++++---- test/test_slave.html | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/browser_manifest.json b/test/browser_manifest.json index 79115d1a4..f11c97c11 100644 --- a/test/browser_manifest.json +++ b/test/browser_manifest.json @@ -1,6 +1,6 @@ [ { - "name":"Firefox 5", + "name":"firefox5", "path":"/Applications/Firefox.app", "type":"firefox" } diff --git a/test/test.py b/test/test.py index 75810f43e..acdd0c552 100644 --- a/test/test.py +++ b/test/test.py @@ -5,6 +5,9 @@ from urlparse import urlparse USAGE_EXAMPLE = "%prog" +# The local web server uses the git repo as the document root. +DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) + ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json' @@ -73,15 +76,14 @@ class PDFTestHandler(BaseHTTPRequestHandler): def do_GET(self): url = urlparse(self.path) + print "GET",url # Ignore query string path, _ = url.path, url.query - cwd = os.getcwd() - path = os.path.abspath(os.path.realpath(cwd + os.sep + path)) - cwd = os.path.abspath(cwd) - prefix = os.path.commonprefix(( path, cwd )) + path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) + prefix = os.path.commonprefix(( path, DOC_ROOT )) _, ext = os.path.splitext(path) - if not (prefix == cwd + if not (prefix == DOC_ROOT and os.path.isfile(path) and ext in MIMEs): self.send_error(404) @@ -146,7 +148,7 @@ def makeBrowserCommands(browserManifestFile): def setUp(options): # Only serve files from a pdf.js clone - assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') + assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git') State.masterMode = options.masterMode if options.masterMode and os.path.isdir(TMPDIR): @@ -191,13 +193,11 @@ def setUp(options): State.remaining = len(manifestList) - - for b in testBrowsers: print 'Launching', b.name qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)), - 'http://localhost:8080/test_slave.html?'+ qs)) + 'http://localhost:8080/test/test_slave.html?'+ qs)) def check(task, results, browser): diff --git a/test/test_manifest.json b/test/test_manifest.json index 036b7aafc..e4a7ada81 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1,21 +1,21 @@ [ { "id": "tracemonkey-eq", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 1, "type": "eq" }, { "id": "tracemonkey-fbf", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 2, "type": "fbf" }, { "id": "html5-canvas-cheat-sheet-load", - "file": "tests/canvas.pdf", + "file": "pdfs/canvas.pdf", "rounds": 1, "type": "load" }, { "id": "pdfspec-load", - "file": "tests/pdf.pdf", + "file": "pdfs/pdf.pdf", "link": true, "rounds": 1, "type": "load" diff --git a/test/test_slave.html b/test/test_slave.html index 06b911810..80e374709 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -1,9 +1,9 @@ <html> <head> <title>pdf.js test slave</title> - <script type="text/javascript" src="pdf.js"></script> - <script type="text/javascript" src="fonts.js"></script> - <script type="text/javascript" src="glyphlist.js"></script> + <script type="text/javascript" src="/pdf.js"></script> + <script type="text/javascript" src="/fonts.js"></script> + <script type="text/javascript" src="/glyphlist.js"></script> <script type="application/javascript"> var browser, canvas, currentTask, currentTaskIdx, failure, manifest, pdfDoc, stdout; @@ -137,7 +137,7 @@ function sendTaskResult(snapshot) { var r = new XMLHttpRequest(); // (The POST URI is ignored atm.) - r.open("POST", "submit_task_results", false); + r.open("POST", "/submit_task_results", false); r.setRequestHeader("Content-Type", "application/json"); // XXX async r.send(JSON.stringify(result)); From f31290e00f28dc15ef515446c18461186de36a33 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 10:27:53 -0700 Subject: [PATCH 07/45] Add pdf.pdf to .gitignore --- test/pdfs/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index e69de29bb..ef853ef61 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -0,0 +1 @@ +pdf.pdf From 984c4d4de61c69999bca8c04230843a5681ba53f Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 11:24:36 -0700 Subject: [PATCH 08/45] Add a user.js file Firefox profile. Change HTTP server to run on background thread. --- test/resources/firefox/user.js | 33 ++++++++++++++++++++++++++ test/test.py | 42 ++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 test/resources/firefox/user.js diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js new file mode 100644 index 000000000..55d9ced33 --- /dev/null +++ b/test/resources/firefox/user.js @@ -0,0 +1,33 @@ +user_pref("browser.console.showInPanel", true); +user_pref("browser.dom.window.dump.enabled", true); +user_pref("browser.firstrun.show.localepicker", false); +user_pref("browser.firstrun.show.uidiscovery", false); +user_pref("dom.allow_scripts_to_close_windows", true); +user_pref("dom.disable_open_during_load", false); +user_pref("dom.max_script_run_time", 0); // no slow script dialogs +user_pref("dom.max_chrome_script_run_time", 0); +user_pref("dom.popup_maximum", -1); +user_pref("dom.send_after_paint_to_content", true); +user_pref("dom.successive_dialog_time_limit", 0); +user_pref("security.warn_submit_insecure", false); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("shell.checkDefaultClient", false); +user_pref("browser.warnOnQuit", false); +user_pref("accessibility.typeaheadfind.autostart", false); +user_pref("javascript.options.showInConsole", true); +user_pref("devtools.errorconsole.enabled", true); +user_pref("layout.debug.enable_data_xbl", true); +user_pref("browser.EULA.override", true); +user_pref("javascript.options.tracejit.content", true); +user_pref("javascript.options.methodjit.content", true); +user_pref("javascript.options.jitprofiling.content", true); +user_pref("javascript.options.methodjit_always", false); +user_pref("gfx.color_management.force_srgb", true); +user_pref("network.manage-offline-status", false); +user_pref("test.mousescroll", true); +user_pref("network.http.prompt-temp-redirect", false); +user_pref("media.cache_size", 100); +user_pref("security.warn_viewing_mixed", false); +user_pref("app.update.enabled", false); +user_pref("browser.panorama.experienced_first_run", true); // Assume experienced +user_pref("dom.w3c_touch_events.enabled", true); \ No newline at end of file diff --git a/test/test.py b/test/test.py index d751a58be..c6bb637a2 100644 --- a/test/test.py +++ b/test/test.py @@ -1,5 +1,6 @@ -import json, platform, os, sys, subprocess, urllib, urllib2 +import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import SocketServer from optparse import OptionParser from urlparse import urlparse @@ -69,8 +70,11 @@ class Result: self.snapshot = snapshot self.failure = failure +class TestServer(SocketServer.TCPServer): + allow_reuse_address = True class PDFTestHandler(BaseHTTPRequestHandler): + # Disable annoying noise by default def log_request(code=0, size=0): if VERBOSE: @@ -78,7 +82,6 @@ class PDFTestHandler(BaseHTTPRequestHandler): def do_GET(self): url = urlparse(self.path) - print "GET",url # Ignore query string path, _ = url.path, url.query path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) @@ -143,6 +146,19 @@ class BrowserCommand(): def _fixupMacPath(self): self.path = self.path + "/Contents/MacOS/firefox-bin" + def setup(self): + self.tempDir = tempfile.mkdtemp() + self.profileDir = os.path.join(self.tempDir, "profile") + shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), + self.profileDir) + + def teardown(self): + shutil.rmtree(self.tempDir) + + def start(self, url): + cmds = [self.path, "-no-remote", "-profile", self.profileDir, url] + subprocess.call(cmds) + def makeBrowserCommands(browserManifestFile): with open(browserManifestFile) as bmf: browsers = [BrowserCommand(browser) for browser in json.load(bmf)] @@ -196,11 +212,13 @@ def setUp(options): State.remaining = len(testBrowsers) * len(manifestList) for b in testBrowsers: - print 'Launching', b.name - qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) - subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)), - 'http://localhost:8080/test/test_slave.html?'+ qs)) - + try: + b.setup() + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + b.start('http://localhost:8080/test/test_slave.html?'+ qs) + finally: + b.teardown() def check(task, results, browser): failed = False @@ -350,12 +368,12 @@ def main(): if options == None: sys.exit(1) - setUp(options) - - server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) - while not State.done: - server.handle_request() + httpd = TestServer(('127.0.0.1', 8080), PDFTestHandler) + httpd_thread = threading.Thread(target=httpd.serve_forever) + httpd_thread.setDaemon(True) + httpd_thread.start() + setUp(options) processResults() if __name__ == '__main__': From ed574cb6c0cc0ab161723cecbed6c01a0f55f73d Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 13:12:22 -0700 Subject: [PATCH 09/45] Add SpecialPowers extension to allow the browser to quit from content, and a bunch of other exciting things. --- test/browser_manifest.json | 5 + .../chrome.manifest | 4 + .../specialpowers/content/specialpowers.js | 372 ++++++++++++++++++ .../components/SpecialPowersObserver.js | 1 + .../special-powers@mozilla.org/install.rdf | 26 ++ test/resources/firefox/user.js | 3 +- test/test.py | 4 +- test/test_slave.html | 5 +- 8 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js create mode 120000 test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js create mode 100644 test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf diff --git a/test/browser_manifest.json b/test/browser_manifest.json index f11c97c11..a396b01ce 100644 --- a/test/browser_manifest.json +++ b/test/browser_manifest.json @@ -3,5 +3,10 @@ "name":"firefox5", "path":"/Applications/Firefox.app", "type":"firefox" + }, + { + "name":"firefox6", + "path":"/Users/sayrer/firefoxen/Aurora.app", + "type":"firefox" } ] \ No newline at end of file diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest new file mode 100644 index 000000000..614f31c3a --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome.manifest @@ -0,0 +1,4 @@ +content specialpowers chrome/specialpowers/content/ +component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js +contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1} +category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1 diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js new file mode 100644 index 000000000..538b104eb --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/chrome/specialpowers/content/specialpowers.js @@ -0,0 +1,372 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Special Powers code + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Clint Talbert cmtalbert@gmail.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK *****/ +/* This code is loaded in every child process that is started by mochitest in + * order to be used as a replacement for UniversalXPConnect + */ + +var Ci = Components.interfaces; +var Cc = Components.classes; + +function SpecialPowers(window) { + this.window = window; + bindDOMWindowUtils(this, window); + this._encounteredCrashDumpFiles = []; + this._unexpectedCrashDumpFiles = { }; + this._crashDumpDir = null; + this._pongHandlers = []; + this._messageListener = this._messageReceived.bind(this); + addMessageListener("SPPingService", this._messageListener); +} + +function bindDOMWindowUtils(sp, window) { + var util = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // This bit of magic brought to you by the letters + // B Z, and E, S and the number 5. + // + // Take all of the properties on the nsIDOMWindowUtils-implementing + // object, and rebind them onto a new object with a stub that uses + // apply to call them from this privileged scope. This way we don't + // have to explicitly stub out new methods that appear on + // nsIDOMWindowUtils. + var proto = Object.getPrototypeOf(util); + var target = {}; + function rebind(desc, prop) { + if (prop in desc && typeof(desc[prop]) == "function") { + var oldval = desc[prop]; + desc[prop] = function() { return oldval.apply(util, arguments); }; + } + } + for (var i in proto) { + var desc = Object.getOwnPropertyDescriptor(proto, i); + rebind(desc, "get"); + rebind(desc, "set"); + rebind(desc, "value"); + Object.defineProperty(target, i, desc); + } + sp.DOMWindowUtils = target; +} + +SpecialPowers.prototype = { + toString: function() { return "[SpecialPowers]"; }, + sanityCheck: function() { return "foo"; }, + + // This gets filled in in the constructor. + DOMWindowUtils: undefined, + + // Mimic the get*Pref API + getBoolPref: function(aPrefName) { + return (this._getPref(aPrefName, 'BOOL')); + }, + getIntPref: function(aPrefName) { + return (this._getPref(aPrefName, 'INT')); + }, + getCharPref: function(aPrefName) { + return (this._getPref(aPrefName, 'CHAR')); + }, + getComplexValue: function(aPrefName, aIid) { + return (this._getPref(aPrefName, 'COMPLEX', aIid)); + }, + + // Mimic the set*Pref API + setBoolPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'BOOL', aValue)); + }, + setIntPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'INT', aValue)); + }, + setCharPref: function(aPrefName, aValue) { + return (this._setPref(aPrefName, 'CHAR', aValue)); + }, + setComplexValue: function(aPrefName, aIid, aValue) { + return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid)); + }, + + // Mimic the clearUserPref API + clearUserPref: function(aPrefName) { + var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""}; + sendSyncMessage('SPPrefService', msg); + }, + + // Private pref functions to communicate to chrome + _getPref: function(aPrefName, aPrefType, aIid) { + var msg = {}; + if (aIid) { + // Overloading prefValue to handle complex prefs + msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]}; + } else { + msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType}; + } + return(sendSyncMessage('SPPrefService', msg)[0]); + }, + _setPref: function(aPrefName, aPrefType, aValue, aIid) { + var msg = {}; + if (aIid) { + msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]}; + } else { + msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue}; + } + return(sendSyncMessage('SPPrefService', msg)[0]); + }, + + //XXX: these APIs really ought to be removed, they're not e10s-safe. + // (also they're pretty Firefox-specific) + _getTopChromeWindow: function(window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .QueryInterface(Ci.nsIDOMChromeWindow); + }, + _getDocShell: function(window) { + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + }, + _getMUDV: function(window) { + return this._getDocShell(window).contentViewer + .QueryInterface(Ci.nsIMarkupDocumentViewer); + }, + _getAutoCompletePopup: function(window) { + return this._getTopChromeWindow(window).document + .getElementById("PopupAutoComplete"); + }, + addAutoCompletePopupEventListener: function(window, listener) { + this._getAutoCompletePopup(window).addEventListener("popupshowing", + listener, + false); + }, + removeAutoCompletePopupEventListener: function(window, listener) { + this._getAutoCompletePopup(window).removeEventListener("popupshowing", + listener, + false); + }, + isBackButtonEnabled: function(window) { + return !this._getTopChromeWindow(window).document + .getElementById("Browser:Back") + .hasAttribute("disabled"); + }, + + addChromeEventListener: function(type, listener, capture, allowUntrusted) { + addEventListener(type, listener, capture, allowUntrusted); + }, + removeChromeEventListener: function(type, listener, capture) { + removeEventListener(type, listener, capture); + }, + + getFullZoom: function(window) { + return this._getMUDV(window).fullZoom; + }, + setFullZoom: function(window, zoom) { + this._getMUDV(window).fullZoom = zoom; + }, + getTextZoom: function(window) { + return this._getMUDV(window).textZoom; + }, + setTextZoom: function(window, zoom) { + this._getMUDV(window).textZoom = zoom; + }, + + createSystemXHR: function() { + return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + }, + + gc: function() { + this.DOMWindowUtils.garbageCollect(); + }, + + hasContentProcesses: function() { + try { + var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); + return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + } catch (e) { + return true; + } + }, + + registerProcessCrashObservers: function() { + addMessageListener("SPProcessCrashService", this._messageListener); + sendSyncMessage("SPProcessCrashService", { op: "register-observer" }); + }, + + _messageReceived: function(aMessage) { + switch (aMessage.name) { + case "SPProcessCrashService": + if (aMessage.json.type == "crash-observed") { + var self = this; + aMessage.json.dumpIDs.forEach(function(id) { + self._encounteredCrashDumpFiles.push(id + ".dmp"); + self._encounteredCrashDumpFiles.push(id + ".extra"); + }); + } + break; + + case "SPPingService": + if (aMessage.json.op == "pong") { + var handler = this._pongHandlers.shift(); + if (handler) { + handler(); + } + } + break; + } + return true; + }, + + removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) { + var success = true; + if (aExpectingProcessCrash) { + var message = { + op: "delete-crash-dump-files", + filenames: this._encounteredCrashDumpFiles + }; + if (!sendSyncMessage("SPProcessCrashService", message)[0]) { + success = false; + } + } + this._encounteredCrashDumpFiles.length = 0; + return success; + }, + + findUnexpectedCrashDumpFiles: function() { + var self = this; + var message = { + op: "find-crash-dump-files", + crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles + }; + var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0]; + crashDumpFiles.forEach(function(aFilename) { + self._unexpectedCrashDumpFiles[aFilename] = true; + }); + return crashDumpFiles; + }, + + executeAfterFlushingMessageQueue: function(aCallback) { + this._pongHandlers.push(aCallback); + sendAsyncMessage("SPPingService", { op: "ping" }); + }, + + executeSoon: function(aFunc) { + var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.mainThread.dispatch({ + run: function() { + aFunc(); + } + }, Ci.nsIThread.DISPATCH_NORMAL); + }, + + /* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js + * by Bob Clary, Jeff Walden, and Robert Sayre. + */ + quitApplication: function() { + function canQuitApplication() + { + var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + if (!os) + return true; + + try { + var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); + os.notifyObservers(cancelQuit, "quit-application-requested", null); + + // Something aborted the quit process. + if (cancelQuit.data) + return false; + } catch (ex) {} + return true; + } + + if (!canQuitApplication()) + return false; + + var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup); + appService.quit(Ci.nsIAppStartup.eForceQuit); + return true; + } +}; + +// Expose everything but internal APIs (starting with underscores) to +// web content. +SpecialPowers.prototype.__exposedProps__ = {}; +for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) { + SpecialPowers.prototype.__exposedProps__[i] = "r"; +} + +// Attach our API to the window. +function attachSpecialPowersToWindow(aWindow) { + try { + if ((aWindow !== null) && + (aWindow !== undefined) && + (aWindow.wrappedJSObject) && + !(aWindow.wrappedJSObject.SpecialPowers)) { + aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow); + } + } catch(ex) { + dump("TEST-INFO | specialpowers.js | Failed to attach specialpowers to window exception: " + ex + "\n"); + } +} + +// This is a frame script, so it may be running in a content process. +// In any event, it is targeted at a specific "tab", so we listen for +// the DOMWindowCreated event to be notified about content windows +// being created in this context. + +function SpecialPowersManager() { + addEventListener("DOMWindowCreated", this, false); +} + +SpecialPowersManager.prototype = { + handleEvent: function handleEvent(aEvent) { + var window = aEvent.target.defaultView; + + // Need to make sure we are called on what we care about - + // content windows. DOMWindowCreated is called on *all* HTMLDocuments, + // some of which belong to chrome windows or other special content. + // + var uri = window.document.documentURIObject; + if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") { + return; + } + + attachSpecialPowersToWindow(window); + } +}; + +var specialpowersmanager = new SpecialPowersManager(); diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js new file mode 120000 index 000000000..6f90832fb --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js @@ -0,0 +1 @@ +/Users/sayrer/dev/mozilla-central/testing/mochitest/specialpowers/components/SpecialPowersObserver.js \ No newline at end of file diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf new file mode 100644 index 000000000..db8de988e --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf @@ -0,0 +1,26 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>special-powers@mozilla.org</em:id> + <em:version>2010.07.23</em:version> + <em:type>2</em:type> + + <!-- Target Application this extension can install into, + with minimum and maximum supported versions. --> + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>3.0</em:minVersion> + <em:maxVersion>7.0a1</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Special Powers</em:name> + <em:description>Special powers for use in testing.</em:description> + <em:creator>Mozilla</em:creator> + </Description> +</RDF> diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index 55d9ced33..2085bcbef 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -30,4 +30,5 @@ user_pref("media.cache_size", 100); user_pref("security.warn_viewing_mixed", false); user_pref("app.update.enabled", false); user_pref("browser.panorama.experienced_first_run", true); // Assume experienced -user_pref("dom.w3c_touch_events.enabled", true); \ No newline at end of file +user_pref("dom.w3c_touch_events.enabled", true); +user_pref("extensions.checkCompatibility", false); \ No newline at end of file diff --git a/test/test.py b/test/test.py index c6bb637a2..5a869530e 100644 --- a/test/test.py +++ b/test/test.py @@ -33,7 +33,8 @@ class TestOptions(OptionParser): def verifyOptions(self, options): if options.masterMode and options.manifestFile: self.error("--masterMode and --manifestFile must not be specified at the same time.") - options.manifestFile = DEFAULT_MANIFEST_FILE + if not options.manifestFile: + options.manifestFile = DEFAULT_MANIFEST_FILE return options def prompt(question): @@ -149,6 +150,7 @@ class BrowserCommand(): def setup(self): self.tempDir = tempfile.mkdtemp() self.profileDir = os.path.join(self.tempDir, "profile") + print self.profileDir shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), self.profileDir) diff --git a/test/test_slave.html b/test/test_slave.html index 6ab84e52f..1053025e1 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -151,7 +151,10 @@ function done() { log("Done!\n"); setTimeout(function() { document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>"; - window.close(); + if (window.SpecialPowers) + SpecialPowers.quitApplication(); + else + window.close(); }, 100 ); From d19a6ef9838d353d39cbd0a1bd5410e06251de5c Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 13:47:43 -0700 Subject: [PATCH 10/45] Add sample manifests. Also make a browser path argument, so you can just specify one browser without messing with a manifest. --- .../browser_manifest.json.mac} | 2 -- test/test.py | 25 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) rename test/{browser_manifest.json => resources/browser_manifests/browser_manifest.json.mac} (78%) diff --git a/test/browser_manifest.json b/test/resources/browser_manifests/browser_manifest.json.mac similarity index 78% rename from test/browser_manifest.json rename to test/resources/browser_manifests/browser_manifest.json.mac index a396b01ce..1aad32d26 100644 --- a/test/browser_manifest.json +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -2,11 +2,9 @@ { "name":"firefox5", "path":"/Applications/Firefox.app", - "type":"firefox" }, { "name":"firefox6", "path":"/Users/sayrer/firefoxen/Aurora.app", - "type":"firefox" } ] \ No newline at end of file diff --git a/test/test.py b/test/test.py index 5a869530e..d7a0fa674 100644 --- a/test/test.py +++ b/test/test.py @@ -11,7 +11,6 @@ DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' -DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json' EQLOG_FILE = 'eq.log' REFDIR = 'ref' TMPDIR = 'tmp' @@ -24,10 +23,11 @@ class TestOptions(OptionParser): help="Run the script in master mode.", default=False) self.add_option("--manifestFile", action="store", type="string", dest="manifestFile", help="A JSON file in the form of test_manifest.json (the default).") + self.add_option("-b", "--browser", action="store", type="string", dest="browser", + help="The path to a single browser (right now, only Firefox is supported).") self.add_option("--browserManifestFile", action="store", type="string", dest="browserManifestFile", - help="A JSON file in the form of browser_manifest.json (the default).", - default=DEFAULT_BROWSER_MANIFEST_FILE) + help="A JSON file in the form of those found in resources/browser_manifests") self.set_usage(USAGE_EXAMPLE) def verifyOptions(self, options): @@ -35,6 +35,8 @@ class TestOptions(OptionParser): self.error("--masterMode and --manifestFile must not be specified at the same time.") if not options.manifestFile: options.manifestFile = DEFAULT_MANIFEST_FILE + if options.browser and options.browserManifestFile: + print "Warning: ignoring browser argument since manifest file was also supplied" return options def prompt(question): @@ -136,16 +138,15 @@ class BrowserCommand(): def __init__(self, browserRecord): self.name = browserRecord["name"] self.path = browserRecord["path"] - self.type = browserRecord["type"] - if platform.system() == "Darwin" and self.path.endswith(".app"): + if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): self._fixupMacPath() if not os.path.exists(self.path): throw("Path to browser '%s' does not exist." % self.path) def _fixupMacPath(self): - self.path = self.path + "/Contents/MacOS/firefox-bin" + self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin") def setup(self): self.tempDir = tempfile.mkdtemp() @@ -158,7 +159,7 @@ class BrowserCommand(): shutil.rmtree(self.tempDir) def start(self, url): - cmds = [self.path, "-no-remote", "-profile", self.profileDir, url] + cmds = [self.path, "-foreground", "-no-remote", "-profile", self.profileDir, url] subprocess.call(cmds) def makeBrowserCommands(browserManifestFile): @@ -179,8 +180,14 @@ def setUp(options): assert not os.path.isdir(TMPDIR) - testBrowsers = makeBrowserCommands(options.browserManifestFile) - + testBrowsers = [] + if options.browserManifestFile: + testBrowsers = makeBrowserCommands(options.browserManifestFile) + elif options.browser: + testBrowsers = [BrowserCommand({"path":options.browser, "name":"firefox"})] + else: + print "No test browsers found. Use --browserManifest or --browser args." + with open(options.manifestFile) as mf: manifestList = json.load(mf) From 1d5e28347fdb0c5e7f69f4ccc823fb81f654214e Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 13:59:38 -0700 Subject: [PATCH 11/45] Cleanup newlines and fix a mistakenly symlinked file. --- .../browser_manifest.json.mac | 2 +- .../components/SpecialPowersObserver.js | 294 +++++++++++++++++- test/resources/firefox/user.js | 2 +- 3 files changed, 295 insertions(+), 3 deletions(-) mode change 120000 => 100755 test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac index 1aad32d26..c287ab32a 100644 --- a/test/resources/browser_manifests/browser_manifest.json.mac +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -7,4 +7,4 @@ "name":"firefox6", "path":"/Users/sayrer/firefoxen/Aurora.app", } -] \ No newline at end of file +] diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js deleted file mode 120000 index 6f90832fb..000000000 --- a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js +++ /dev/null @@ -1 +0,0 @@ -/Users/sayrer/dev/mozilla-central/testing/mochitest/specialpowers/components/SpecialPowersObserver.js \ No newline at end of file diff --git a/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js new file mode 100755 index 000000000..90655e2e7 --- /dev/null +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/components/SpecialPowersObserver.js @@ -0,0 +1,293 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Special Powers code + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jesse Ruderman <jruderman@mozilla.com> + * Robert Sayre <sayrer@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK *****/ + +// Based on: +// https://bugzilla.mozilla.org/show_bug.cgi?id=549539 +// https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661 +// https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3 +// http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240 +// https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +const Cc = Components.classes; +const Ci = Components.interfaces; + +const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js" + +/** + * Special Powers Exception - used to throw exceptions nicely + **/ +function SpecialPowersException(aMsg) { + this.message = aMsg; + this.name = "SpecialPowersException"; +} + +SpecialPowersException.prototype.toString = function() { + return this.name + ': "' + this.message + '"'; +}; + +/* XPCOM gunk */ +function SpecialPowersObserver() { + this._isFrameScriptLoaded = false; + this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"]. + getService(Ci.nsIChromeFrameMessageManager); +} + +SpecialPowersObserver.prototype = { + classDescription: "Special powers Observer for use in testing.", + classID: Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"), + contractID: "@mozilla.org/special-powers-observer;1", + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]), + _xpcom_categories: [{category: "profile-after-change", service: true }], + + observe: function(aSubject, aTopic, aData) + { + switch (aTopic) { + case "profile-after-change": + this.init(); + break; + + case "chrome-document-global-created": + if (!this._isFrameScriptLoaded) { + // Register for any messages our API needs us to handle + this._messageManager.addMessageListener("SPPrefService", this); + this._messageManager.addMessageListener("SPProcessCrashService", this); + this._messageManager.addMessageListener("SPPingService", this); + + this._messageManager.loadFrameScript(CHILD_SCRIPT, true); + this._isFrameScriptLoaded = true; + } + break; + + case "xpcom-shutdown": + this.uninit(); + break; + + case "plugin-crashed": + case "ipc:content-shutdown": + function addDumpIDToMessage(propertyName) { + var id = aSubject.getPropertyAsAString(propertyName); + if (id) { + message.dumpIDs.push(id); + } + } + + var message = { type: "crash-observed", dumpIDs: [] }; + aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2); + if (aTopic == "plugin-crashed") { + addDumpIDToMessage("pluginDumpID"); + addDumpIDToMessage("browserDumpID"); + } else { // ipc:content-shutdown + addDumpIDToMessage("dumpID"); + } + this._messageManager.sendAsyncMessage("SPProcessCrashService", message); + break; + } + }, + + init: function() + { + var obs = Services.obs; + obs.addObserver(this, "xpcom-shutdown", false); + obs.addObserver(this, "chrome-document-global-created", false); + }, + + uninit: function() + { + var obs = Services.obs; + obs.removeObserver(this, "chrome-document-global-created", false); + this.removeProcessCrashObservers(); + }, + + addProcessCrashObservers: function() { + if (this._processCrashObserversRegistered) { + return; + } + + Services.obs.addObserver(this, "plugin-crashed", false); + Services.obs.addObserver(this, "ipc:content-shutdown", false); + this._processCrashObserversRegistered = true; + }, + + removeProcessCrashObservers: function() { + if (!this._processCrashObserversRegistered) { + return; + } + + Services.obs.removeObserver(this, "plugin-crashed"); + Services.obs.removeObserver(this, "ipc:content-shutdown"); + this._processCrashObserversRegistered = false; + }, + + getCrashDumpDir: function() { + if (!this._crashDumpDir) { + var directoryService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile); + this._crashDumpDir.append("minidumps"); + } + return this._crashDumpDir; + }, + + deleteCrashDumpFiles: function(aFilenames) { + var crashDumpDir = this.getCrashDumpDir(); + if (!crashDumpDir.exists()) { + return false; + } + + var success = aFilenames.length != 0; + aFilenames.forEach(function(crashFilename) { + var file = crashDumpDir.clone(); + file.append(crashFilename); + if (file.exists()) { + file.remove(false); + } else { + success = false; + } + }); + return success; + }, + + findCrashDumpFiles: function(aToIgnore) { + var crashDumpDir = this.getCrashDumpDir(); + var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries; + if (!entries) { + return []; + } + + var crashDumpFiles = []; + while (entries.hasMoreElements()) { + var file = entries.getNext().QueryInterface(Ci.nsIFile); + var path = String(file.path); + if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) { + crashDumpFiles.push(path); + } + } + return crashDumpFiles.concat(); + }, + + /** + * messageManager callback function + * This will get requests from our API in the window and process them in chrome for it + **/ + receiveMessage: function(aMessage) { + switch(aMessage.name) { + case "SPPrefService": + var prefs = Services.prefs; + var prefType = aMessage.json.prefType.toUpperCase(); + var prefName = aMessage.json.prefName; + var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null; + + if (aMessage.json.op == "get") { + if (!prefName || !prefType) + throw new SpecialPowersException("Invalid parameters for get in SPPrefService"); + } else if (aMessage.json.op == "set") { + if (!prefName || !prefType || prefValue === null) + throw new SpecialPowersException("Invalid parameters for set in SPPrefService"); + } else if (aMessage.json.op == "clear") { + if (!prefName) + throw new SpecialPowersException("Invalid parameters for clear in SPPrefService"); + } else { + throw new SpecialPowersException("Invalid operation for SPPrefService"); + } + // Now we make the call + switch(prefType) { + case "BOOL": + if (aMessage.json.op == "get") + return(prefs.getBoolPref(prefName)); + else + return(prefs.setBoolPref(prefName, prefValue)); + case "INT": + if (aMessage.json.op == "get") + return(prefs.getIntPref(prefName)); + else + return(prefs.setIntPref(prefName, prefValue)); + case "CHAR": + if (aMessage.json.op == "get") + return(prefs.getCharPref(prefName)); + else + return(prefs.setCharPref(prefName, prefValue)); + case "COMPLEX": + if (aMessage.json.op == "get") + return(prefs.getComplexValue(prefName, prefValue[0])); + else + return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1])); + case "": + if (aMessage.json.op == "clear") { + prefs.clearUserPref(prefName); + return; + } + } + break; + + case "SPProcessCrashService": + switch (aMessage.json.op) { + case "register-observer": + this.addProcessCrashObservers(); + break; + case "unregister-observer": + this.removeProcessCrashObservers(); + break; + case "delete-crash-dump-files": + return this.deleteCrashDumpFiles(aMessage.json.filenames); + case "find-crash-dump-files": + return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore); + default: + throw new SpecialPowersException("Invalid operation for SPProcessCrashService"); + } + break; + + case "SPPingService": + if (aMessage.json.op == "ping") { + aMessage.target + .QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .messageManager + .sendAsyncMessage("SPPingService", { op: "pong" }); + } + break; + + default: + throw new SpecialPowersException("Unrecognized Special Powers API"); + } + } +}; + +const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]); diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index 2085bcbef..d4b9d4130 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -31,4 +31,4 @@ user_pref("security.warn_viewing_mixed", false); user_pref("app.update.enabled", false); user_pref("browser.panorama.experienced_first_run", true); // Assume experienced user_pref("dom.w3c_touch_events.enabled", true); -user_pref("extensions.checkCompatibility", false); \ No newline at end of file +user_pref("extensions.checkCompatibility", false); From 986ef148c47a6d5199a8962c8d07351a4630ff6a Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Tue, 21 Jun 2011 23:33:11 +0200 Subject: [PATCH 12/45] Backup work --- canvas_proxy.js | 109 +++++++++++++++++++++++++++++++++++++++++++ pdf.js | 13 +++++- viewer_worker.html | 110 +++++++++++++++++++++++++++++++++++++++++++ worker.js | 113 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 canvas_proxy.js create mode 100644 viewer_worker.html create mode 100644 worker.js diff --git a/canvas_proxy.js b/canvas_proxy.js new file mode 100644 index 000000000..1b100beae --- /dev/null +++ b/canvas_proxy.js @@ -0,0 +1,109 @@ +function CanvasProxy(width, height) { + var stack = this.$stack = []; + + // Expose only the minimum of the canvas object - there is no dom to do + // more here. + this.canvas = { + width: width, + height: height + } + + var ctxFunc = [ + "createRadialGradient", + "arcTo", + "arc", + "fillText", + "strokeText", + "drawImage", + "getImageData", + "putImageData", + "createImageData", + "drawWindow", + "save", + "restore", + "scale", + "rotate", + "translate", + "transform", + "setTransform", + "createLinearGradient", + "createPattern", + "clearRect", + "fillRect", + "strokeRect", + "beginPath", + "closePath", + "moveTo", + "lineTo", + "quadraticCurveTo", + "bezierCurveTo", + "rect", + "fill", + "stroke", + "clip", + "measureText", + "isPointInPath" + ]; + function buildFuncCall(name) { + return function() { + console.log("funcCall", name) + stack.push([name, Array.prototype.slice.call(arguments)]); + } + } + var name; + for (var i = 0; i < ctxFunc.length; i++) { + name = ctxFunc[i]; + this[name] = buildFuncCall(name); + } + + var ctxProp = { + // "canvas" + "globalAlpha": "1", + "globalCompositeOperation": "source-over", + "strokeStyle": "#000000", + "fillStyle": "#000000", + "lineWidth": "1", + "lineCap": "butt", + "lineJoin": "miter", + "miterLimit": "10", + "shadowOffsetX": "0", + "shadowOffsetY": "0", + "shadowBlur": "0", + "shadowColor": "rgba(0, 0, 0, 0)", + "font": "10px sans-serif", + "textAlign": "start", + "textBaseline": "alphabetic", + "mozTextStyle": "10px sans-serif", + "mozImageSmoothingEnabled": "true", + "DRAWWINDOW_DRAW_CARET": "1", + "DRAWWINDOW_DO_NOT_FLUSH": "2", + "DRAWWINDOW_DRAW_VIEW": "4", + "DRAWWINDOW_USE_WIDGET_LAYERS": "8", + "DRAWWINDOW_ASYNC_DECODE_IMAGES": "16", + } + + function buildGetter(name) { + return function() { + return this["$" + name]; + } + } + + function buildSetter(name) { + return function(value) { + stack.push(["$", name, value]); + return this["$" + name] = value; + } + } + + for (var name in ctxProp) { + this["$" + name] = ctxProp[name]; + this.__defineGetter__(name, buildGetter(name)); + this.__defineSetter__(name, buildSetter(name)); + } +} + +CanvasProxy.prototype.flush = function() { + postMessage("canvas_proxy_stack"); + postMessage(JSON.stringify(this.$stack)); + this.$stack.length = 0; +} diff --git a/pdf.js b/pdf.js index 326c31234..09d1c874e 100644 --- a/pdf.js +++ b/pdf.js @@ -2277,7 +2277,10 @@ var CanvasGraphics = (function() { this.pendingClip = null; this.res = null; this.xobjs = null; - this.map = { + } + + constructor.prototype = { + map: { // Graphics state w: "setLineWidth", J: "setLineCap", @@ -2634,7 +2637,9 @@ var CanvasGraphics = (function() { } var fn = Function("objpool", src); - return function (gfx) { fn.call(gfx, objpool); }; + var ret = function (gfx) { fn.call(gfx, objpool); }; + ret.src = src; + return ret; }, endDrawing: function() { @@ -3041,6 +3046,7 @@ var CanvasGraphics = (function() { shadingFill: function(entryRef) { var xref = this.xref; var res = this.res; + var shadingRes = xref.fetchIfRef(res.get("Shading")); if (!shadingRes) error("No shading resource found"); @@ -3468,6 +3474,7 @@ var ColorSpace = (function() { break; case "ICCBased": var dict = stream.dict; + this.stream = stream; this.dict = dict; this.numComps = dict.get("N"); @@ -3574,6 +3581,7 @@ var PDFFunction = (function() { v = encode[i2] + ((v - domain[i2]) * (encode[i2 + 1] - encode[i2]) / (domain[i2 + 1] - domain[i2])); + // clip to the size args[i] = clip(v, 0, size[i] - 1); } @@ -3601,6 +3609,7 @@ var PDFFunction = (function() { // decode v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / ((1 << bps) - 1)); + // clip to the domain output.push(clip(v, range[i2], range[i2 + 1])); } diff --git a/viewer_worker.html b/viewer_worker.html new file mode 100644 index 000000000..f9e1f0b32 --- /dev/null +++ b/viewer_worker.html @@ -0,0 +1,110 @@ +<html> + <head> + <title>Simple pdf.js page viewer worker</title> +<script> +var myWorker = new Worker('worker.js'); + +// var array = new Uint8Array(2); +// array[0] = 1; +// array[1] = 300; +// +const WAIT = 0; +const CANVAS_PROXY_STACK = 1; +const LOG = 2; + +var onMessageState = WAIT; +myWorker.onmessage = function(event) { + var data = event.data; + console.log("onMessageRaw", data); + switch (onMessageState) { + case WAIT: + if (typeof data != "string") { + throw "expecting to get an string"; + } + switch (data) { + case "log": + onMessageState = LOG; + return; + case "canvas_proxy_stack": + onMessageState = CANVAS_PROXY_STACK; + return; + default: + throw "unkown state: " + data + } + break; + + case LOG: + console.log.apply(console, JSON.parse(data)); + onMessageState = WAIT; + break; + + case CANVAS_PROXY_STACK: + var stack = JSON.parse(data); + for (var i = 0; i < stack.length; i++) { + var opp = stack[i]; + if (opp[0] == "$") { + console.log("set property", opp[1], opp[2]); + ctx[opp[1]] = opp[2]; + } else { + console.log("execute", opp[0], opp[1]); + ctx[opp[0]].apply(ctx, opp[1]); + } + } + onMessageState = WAIT; + break; + } +} +// +// myWorker.postMessage(array); + +function open(url) { + document.title = url; + var req = new XMLHttpRequest(); + req.open("GET", url); + req.mozResponseType = req.responseType = "arraybuffer"; + req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; + req.onreadystatechange = function() { + if (req.readyState == 4 && req.status == req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || + req.responseArrayBuffer || req.response; + myWorker.postMessage(data); + } + }; + req.send(null); +} + +window.onload = function() { + var ctx = window.ctx = document.getElementById("canvas").getContext("2d"); + // for (var name in ctx) { + // if (!(ctx[name] instanceof Function)) { + // console.log('"' + name + '": "' + ctx[name] + '",'); + // } + // } + open("compressed.tracemonkey-pldi-09.pdf"); +} +</script> + <link rel="stylesheet" href="viewer.css"></link> + </head> + + <body> + <div id="controls"> + <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input> + <!-- This only opens supported PDFs from the source path... + -- Can we use JSONP to overcome the same-origin restrictions? --> + <button onclick="prevPage();">Previous</button> + <button onclick="nextPage();">Next</button> + <input type="text" id="pageNumber" onchange="gotoPage(this.value);" + value="1" size="4"></input> + <span id="numPages">--</span> + <span id="info"></span> + </div> + + <div id="viewer"> + <!-- Canvas dimensions must be specified in CSS pixels. CSS pixels + are always 96 dpi. 816x1056 is 8.5x11in at 96dpi. --> + <!-- We're rendering here at 1.5x scale. --> + <canvas id="canvas" width="1224" height="1584"></canvas> + </div> + </body> +</html> + diff --git a/worker.js b/worker.js new file mode 100644 index 000000000..fdc762afd --- /dev/null +++ b/worker.js @@ -0,0 +1,113 @@ +"use strict"; + +function log() { + var args = Array.prototype.slice.call(arguments); + postMessage("log"); + postMessage(JSON.stringify(args)) +} + +var console = { + log: log +} + +importScripts("canvas_proxy.js"); +importScripts("pdf.js"); +importScripts("fonts.js"); +importScripts("glyphlist.js") + +// var array = new Uint8Array(2); +// array[0] = 1; +// array[1] = 300; +// postMessage(array); + +var timer = null; +function tic() { + timer = Date.now(); +} + +function toc(msg) { + log("Took ", (Date.now() - timer)); + timer = null; +} + + +var canvas = new CanvasProxy(1224, 1584); +// canvas.moveTo(0, 10); +// canvas.lineTo(0, 20); +// canvas.lineTo(500, 500); +// canvas.flush(); +// canvas.stroke(); +// canvas.flush(); +log("test"); + +onmessage = function(event) { + var data = event.data; + var pdfDocument = new PDFDoc(new Stream(data)); + var numPages = pdfDocument.numPages; + + tic(); + // Let's try to render the first page... + var page = pdfDocument.getPage(1); + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + + var gfx = new CanvasGraphics(canvas); + page.compile(gfx, fonts); + toc("compiled page"); + + // + var fontsReady = true; + // Inspect fonts and translate the missing one + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + fontsReady = false; + } + + function delayLoadFont() { + for (var i = 0; i < count; i++) { + if (Fonts[font.name].loading) + return; + } + clearInterval(pageInterval); + page.display(gfx); + + canvas.flush(); + }; + + if (fontsReady) { + delayLoadFont(); + } else { + pageInterval = setInterval(delayLoadFont, 10); + } + postMessage(page.code.src); +} + +// function open(url) { +// var req = new XMLHttpRequest(); +// req.open("GET", url); +// // req.responseType = "arraybuffer"; +// req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200; +// req.onreadystatechange = function() { +// postMessage("loaded"); +// if (req.readyState == 4 && req.status == req.expected) { +// var data = req.mozResponseArrayBuffer || req.mozResponse || +// req.responseArrayBuffer || req.response; +// pdfDocument = new PDFDoc(new Stream(data)); +// numPages = pdfDocument.numPages; +// // document.getElementById("numPages").innerHTML = numPages.toString(); +// // goToPage(pageNum); +// } +// }; +// req.send(null); +// } +// +// open("compressed.tracemonkey-pldi-09.pdf") \ No newline at end of file From e15328800aad995b1a2957ba4f98f9a618a6208f Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 01:28:17 +0200 Subject: [PATCH 13/45] Most working, but once you add the font-css file to the web page, there is no font drawn at all --- canvas_proxy.js | 12 +++- fonts.js | 164 +++++++++++++++++++++++++-------------------- pdf.js | 34 ++++++++-- viewer_worker.html | 95 +++++++++++++++++++++++--- worker.js | 64 +++++++++--------- 5 files changed, 248 insertions(+), 121 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 1b100beae..433166aac 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -42,11 +42,17 @@ function CanvasProxy(width, height) { "stroke", "clip", "measureText", - "isPointInPath" + "isPointInPath", + + "$setCurrentX", + "$addCurrentX", + "$saveCurrentX", + "$restoreCurrentX", + "$showText" ]; function buildFuncCall(name) { return function() { - console.log("funcCall", name) + // console.log("funcCall", name) stack.push([name, Array.prototype.slice.call(arguments)]); } } @@ -103,6 +109,8 @@ function CanvasProxy(width, height) { } CanvasProxy.prototype.flush = function() { + // postMessage("log"); + // postMessage(JSON.stringify([this.$stack.length])); postMessage("canvas_proxy_stack"); postMessage(JSON.stringify(this.$stack)); this.$stack.length = 0; diff --git a/fonts.js b/fonts.js index d5943b7a3..8c0abbcec 100644 --- a/fonts.js +++ b/fonts.js @@ -759,91 +759,109 @@ var Font = (function () { var data = this.font; var fontName = this.name; + var isWorker = (typeof window == "undefined"); /** Hack begin */ + if (!isWorker) { + + // Actually there is not event when a font has finished downloading so + // the following code are a dirty hack to 'guess' when a font is ready + var canvas = document.createElement("canvas"); + var style = "border: 1px solid black; position:absolute; top: " + + (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; + canvas.setAttribute("style", style); + canvas.setAttribute("width", 340); + canvas.setAttribute("heigth", 100); + document.body.appendChild(canvas); + + // Get the font size canvas think it will be for 'spaces' + var ctx = canvas.getContext("2d"); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var testString = " "; + + // When debugging use the characters provided by the charsets to visually + // see what's happening instead of 'spaces' + var debug = false; + if (debug) { + var name = document.createElement("font"); + name.setAttribute("style", "position: absolute; left: 20px; top: " + + (100 * fontCount + 60) + "px"); + name.innerHTML = fontName; + document.body.appendChild(name); + + // Retrieve font charset + var charset = Fonts[fontName].properties.charset || []; + + // if the charset is too small make it repeat a few times + var count = 30; + while (count-- && charset.length <= 30) + charset = charset.concat(charset.slice()); + + for (var i = 0; i < charset.length; i++) { + var unicode = GlyphsUnicode[charset[i]]; + if (!unicode) + continue; + testString += String.fromCharCode(unicode); + } - // Actually there is not event when a font has finished downloading so - // the following code are a dirty hack to 'guess' when a font is ready - var canvas = document.createElement("canvas"); - var style = "border: 1px solid black; position:absolute; top: " + - (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); - - // Get the font size canvas think it will be for 'spaces' - var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var testString = " "; - - // When debugging use the characters provided by the charsets to visually - // see what's happening instead of 'spaces' - var debug = false; - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); - - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; - - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); - - for (var i = 0; i < charset.length; i++) { - var unicode = GlyphsUnicode[charset[i]]; - if (!unicode) - continue; - testString += String.fromCharCode(unicode); - } - - ctx.fillText(testString, 20, 20); - } + ctx.fillText(testString, 20, 20); + } - // Periodicaly check for the width of the testString, it will be - // different once the real font has loaded - var textWidth = ctx.measureText(testString).width; - - var interval = window.setInterval(function canvasInterval(self) { - this.start = this.start || Date.now(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - - // For some reasons the font has not loaded, so mark it loaded for the - // page to proceed but cry - if ((Date.now() - this.start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); - this.start = 0; - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - this.start = 0; - } + // Periodicaly check for the width of the testString, it will be + // different once the real font has loaded + var textWidth = ctx.measureText(testString).width; + + var interval = window.setInterval(function canvasInterval(self) { + this.start = this.start || Date.now(); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + + // For some reasons the font has not loaded, so mark it loaded for the + // page to proceed but cry + if ((Date.now() - this.start) >= kMaxWaitForFontFace) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + warn("Is " + fontName + " for charset: " + charset + " loaded?"); + this.start = 0; + } else if (textWidth != ctx.measureText(testString).width) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + this.start = 0; + } - if (debug) - ctx.fillText(testString, 20, 50); - }, 30, this); + if (debug) + ctx.fillText(testString, 20, 50); + }, 30, this); + } /** Hack end */ - + // // Get the base64 encoding of the binary font data var str = ""; var length = data.length; for (var i = 0; i < length; ++i) str += String.fromCharCode(data[i]); - var base64 = window.btoa(str); - - // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); + if (isWorker) { + postMessage("font"); + postMessage(JSON.stringify({ + str: str, + mimetype: this.mimetype, + fontName: fontName, + })); + + setTimeout(function() { + Fonts[fontName].loading = false; + }, kMaxWaitForFontFace); + } else { + var base64 = window.btoa(str); + + // Add the @font-face rule to the document + var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); + console.log("added font", fontName); + console.log(rule); + } } }; diff --git a/pdf.js b/pdf.js index 09d1c874e..80e9c1930 100644 --- a/pdf.js +++ b/pdf.js @@ -2674,12 +2674,18 @@ var CanvasGraphics = (function() { }, save: function() { this.ctx.save(); + if (this.ctx.$saveCurrentX) { + this.ctx.$saveCurrentX(); + } this.stateStack.push(this.current); this.current = new CanvasExtraState(); }, restore: function() { var prev = this.stateStack.pop(); if (prev) { + if (this.ctx.$restoreCurrentX) { + this.ctx.$restoreCurrentX(); + } this.current = prev; this.ctx.restore(); } @@ -2760,6 +2766,9 @@ var CanvasGraphics = (function() { // Text beginText: function() { this.current.textMatrix = IDENTITY_MATRIX; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0) + } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, @@ -2814,6 +2823,9 @@ var CanvasGraphics = (function() { moveText: function (x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(this.current.x) + } }, setLeadingMoveText: function(x, y) { this.setLeading(-y); @@ -2821,6 +2833,10 @@ var CanvasGraphics = (function() { }, setTextMatrix: function(a, b, c, d, e, f) { this.current.textMatrix = [ a, b, c, d, e, f ]; + + if (this.ctx.$setCurrentX) { + this.$setCurrentX(0) + } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, @@ -2831,11 +2847,15 @@ var CanvasGraphics = (function() { this.ctx.save(); this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); - this.ctx.translate(0, -2 * this.current.y); - text = Fonts.charsToUnicode(text); - this.ctx.fillText(text, this.current.x, this.current.y); - this.current.x += this.ctx.measureText(text).width; + if (this.ctx.$showText) { + this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); + } else { + console.log(text, this.current.x); + text = Fonts.charsToUnicode(text); + this.ctx.fillText(text, 0, 0); + this.current.x += this.ctx.measureText(text).width; + } this.ctx.restore(); }, @@ -2843,7 +2863,11 @@ var CanvasGraphics = (function() { for (var i = 0; i < arr.length; ++i) { var e = arr[i]; if (IsNum(e)) { - this.current.x -= e * 0.001 * this.current.fontSize; + if (this.ctx.$addCurrentX) { + this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize) + } else { + this.current.x -= e * 0.001 * this.current.fontSize; + } } else if (IsString(e)) { this.showText(e); } else { diff --git a/viewer_worker.html b/viewer_worker.html index f9e1f0b32..dde249e55 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -11,11 +11,63 @@ var myWorker = new Worker('worker.js'); const WAIT = 0; const CANVAS_PROXY_STACK = 1; const LOG = 2; +const FONT = 3; + +var currentX = 0; +var currentXStack = []; +var special = { + "$setCurrentX": function(value) { + currentX = value; + }, + + "$addCurrentX": function(value) { + currentX += value; + }, + + "$saveCurrentX": function() { + currentXStack.push(currentX); + }, + + "$restoreCurrentX": function() { + currentX = currentXStack.pop(); + }, + + "$showText": function(y, text) { + console.log(text, currentX, y, this.measureText(text).width); + + this.translate(currentX, -1 * y); + this.fillText(text, 0, 0); + currentX += this.measureText(text).width; + } +} + +function renderProxyCanvas(stack) { + // for (var i = 0; i < stack.length; i++) { + for (var i = 0; i < 1000; i++) { + var opp = stack[i]; + if (opp[0] == "$") { + // console.log("set property", opp[1], opp[2]); + if (opp[1] == "font") { + ctx[opp[1]] = opp[2]; + // console.log("font", opp[2]); + } else { + ctx[opp[1]] = opp[2]; + } + + } else if (opp[0] in special) { + // console.log("sepcial", opp[0], opp[1]) + special[opp[0]].apply(ctx, opp[1]); + } else { + // console.log("execute", opp[0], opp[1]); + ctx[opp[0]].apply(ctx, opp[1]); + } + } +} var onMessageState = WAIT; myWorker.onmessage = function(event) { var data = event.data; - console.log("onMessageRaw", data); + // console.log("onMessageRaw", data); switch (onMessageState) { case WAIT: if (typeof data != "string") { @@ -28,11 +80,31 @@ myWorker.onmessage = function(event) { case "canvas_proxy_stack": onMessageState = CANVAS_PROXY_STACK; return; + case "font": + onMessageState = FONT; + return; default: throw "unkown state: " + data } break; + case FONT: + data = JSON.parse(data); + var base64 = window.btoa(data.str); + + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + + // ONCE you uncomment this, there is no font painted at all :( + // styleSheet.insertRule(rule, styleSheet.length); + + console.log("added font", data.fontName); + // console.log(rule); + onMessageState = WAIT; + break; + case LOG: console.log.apply(console, JSON.parse(data)); onMessageState = WAIT; @@ -40,17 +112,14 @@ myWorker.onmessage = function(event) { case CANVAS_PROXY_STACK: var stack = JSON.parse(data); - for (var i = 0; i < stack.length; i++) { - var opp = stack[i]; - if (opp[0] == "$") { - console.log("set property", opp[1], opp[2]); - ctx[opp[1]] = opp[2]; - } else { - console.log("execute", opp[0], opp[1]); - ctx[opp[0]].apply(ctx, opp[1]); - } - } + console.log("canvas stack", stack.length) + // console.log(stack.length); onMessageState = WAIT; + // return; + + setTimeout(function() { + renderProxyCanvas(stack); + }, 2000); break; } } @@ -75,6 +144,10 @@ function open(url) { window.onload = function() { var ctx = window.ctx = document.getElementById("canvas").getContext("2d"); + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); // for (var name in ctx) { // if (!(ctx[name] instanceof Function)) { // console.log('"' + name + '": "' + ctx[name] + '",'); diff --git a/worker.js b/worker.js index fdc762afd..9ee9409bd 100644 --- a/worker.js +++ b/worker.js @@ -40,6 +40,7 @@ var canvas = new CanvasProxy(1224, 1584); // canvas.flush(); log("test"); +var pageInterval; onmessage = function(event) { var data = event.data; var pdfDocument = new PDFDoc(new Stream(data)); @@ -59,36 +60,39 @@ onmessage = function(event) { // var fontsReady = true; - // Inspect fonts and translate the missing one - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - fontsReady = false; - } - - function delayLoadFont() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) - return; - } - clearInterval(pageInterval); - page.display(gfx); - - canvas.flush(); - }; - - if (fontsReady) { - delayLoadFont(); - } else { - pageInterval = setInterval(delayLoadFont, 10); - } - postMessage(page.code.src); + // Inspect fonts and translate the missing one + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + fontsReady = false; + } + + // function delayLoadFont() { + // for (var i = 0; i < count; i++) { + // if (Fonts[font.name].loading) + // return; + // } + // clearInterval(pageInterval); + // page.display(gfx); + // + // log("flush"); + // canvas.flush(); + // }; + + // if (fontsReady) { + // delayLoadFont(); + // } else { + // pageInterval = setInterval(delayLoadFont, 10); + // } + + page.display(gfx); + canvas.flush(); } // function open(url) { From 61b76c7e87307b5d192712e240fe9b3c8ecfaf46 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 09:15:55 +0200 Subject: [PATCH 14/45] Make fonts getting loaded by a very nasty hack --- fonts.js | 6 +----- pdf.js | 6 +++--- viewer_worker.html | 43 ++++++++++++++++++++++++++++--------------- worker.js | 2 +- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/fonts.js b/fonts.js index 8c0abbcec..c7f54edf9 100644 --- a/fonts.js +++ b/fonts.js @@ -844,13 +844,9 @@ var Font = (function () { postMessage("font"); postMessage(JSON.stringify({ str: str, - mimetype: this.mimetype, fontName: fontName, + mimetype: this.mimetype })); - - setTimeout(function() { - Fonts[fontName].loading = false; - }, kMaxWaitForFontFace); } else { var base64 = window.btoa(str); diff --git a/pdf.js b/pdf.js index 80e9c1930..64b99a33e 100644 --- a/pdf.js +++ b/pdf.js @@ -2835,7 +2835,7 @@ var CanvasGraphics = (function() { this.current.textMatrix = [ a, b, c, d, e, f ]; if (this.ctx.$setCurrentX) { - this.$setCurrentX(0) + this.ctx.$setCurrentX(0) } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; @@ -2851,9 +2851,9 @@ var CanvasGraphics = (function() { if (this.ctx.$showText) { this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); } else { - console.log(text, this.current.x); text = Fonts.charsToUnicode(text); - this.ctx.fillText(text, 0, 0); + this.ctx.translate(this.current.x, -1 * this.current.y); + this.ctx.fillText(Fonts.charsToUnicode(text), 0, 0); this.current.x += this.ctx.measureText(text).width; } diff --git a/viewer_worker.html b/viewer_worker.html index dde249e55..930fb6cd5 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -8,10 +8,6 @@ var myWorker = new Worker('worker.js'); // array[0] = 1; // array[1] = 300; // -const WAIT = 0; -const CANVAS_PROXY_STACK = 1; -const LOG = 2; -const FONT = 3; var currentX = 0; var currentXStack = []; @@ -33,7 +29,7 @@ var special = { }, "$showText": function(y, text) { - console.log(text, currentX, y, this.measureText(text).width); + // console.log(text, currentX, y, this.measureText(text).width); this.translate(currentX, -1 * y); this.fillText(text, 0, 0); @@ -41,14 +37,16 @@ var special = { } } +var gStack; function renderProxyCanvas(stack) { - // for (var i = 0; i < stack.length; i++) { - for (var i = 0; i < 1000; i++) { + for (var i = 0; i < stack.length; i++) { + // for (var i = 0; i < 1000; i++) { var opp = stack[i]; if (opp[0] == "$") { // console.log("set property", opp[1], opp[2]); if (opp[1] == "font") { ctx[opp[1]] = opp[2]; + // ctx.font = "10px 'Verdana Bold Italic'"; // console.log("font", opp[2]); } else { ctx[opp[1]] = opp[2]; @@ -64,7 +62,15 @@ function renderProxyCanvas(stack) { } } +const WAIT = 0; +const CANVAS_PROXY_STACK = 1; +const LOG = 2; +const FONT = 3; + var onMessageState = WAIT; +var fontStr = null; +var first = true; +var intervals = []; myWorker.onmessage = function(event) { var data = event.data; // console.log("onMessageRaw", data); @@ -96,12 +102,15 @@ myWorker.onmessage = function(event) { var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); - // ONCE you uncomment this, there is no font painted at all :( - // styleSheet.insertRule(rule, styleSheet.length); + // *HACK*: this makes the font get loaded on the page. WTF? We + // really have to set the fonts a few time... + var interval = setInterval(function() { + ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial"; + }, 10); + intervals.push(interval); - console.log("added font", data.fontName); - // console.log(rule); onMessageState = WAIT; break; @@ -112,14 +121,18 @@ myWorker.onmessage = function(event) { case CANVAS_PROXY_STACK: var stack = JSON.parse(data); + gStack = stack; console.log("canvas stack", stack.length) - // console.log(stack.length); - onMessageState = WAIT; - // return; + // Shedule a timeout. Hoping the fonts are loaded after 100ms. setTimeout(function() { + // Remove all setIntervals to make the font load. + intervals.forEach(function(inter) { + clearInterval(inter); + }); renderProxyCanvas(stack); - }, 2000); + }, 100); + onMessageState = WAIT; break; } } diff --git a/worker.js b/worker.js index 9ee9409bd..6d34a9d62 100644 --- a/worker.js +++ b/worker.js @@ -48,7 +48,7 @@ onmessage = function(event) { tic(); // Let's try to render the first page... - var page = pdfDocument.getPage(1); + var page = pdfDocument.getPage(8); // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display From fc007b99d0edf3086445cf5d0751f6b0f003073f Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 09:46:11 +0200 Subject: [PATCH 15/45] Introduce ImageCanvas to handle canvas rendering in WebWorker --- canvas_proxy.js | 7 ++++--- fonts.js | 2 -- pdf.js | 41 +++++++++++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 433166aac..ccc95c74a 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -14,9 +14,9 @@ function CanvasProxy(width, height) { "arc", "fillText", "strokeText", - "drawImage", - "getImageData", - "putImageData", + // "drawImage", + // "getImageData", + // "putImageData", "createImageData", "drawWindow", "save", @@ -50,6 +50,7 @@ function CanvasProxy(width, height) { "$restoreCurrentX", "$showText" ]; + function buildFuncCall(name) { return function() { // console.log("funcCall", name) diff --git a/fonts.js b/fonts.js index c7f54edf9..9c9201b72 100644 --- a/fonts.js +++ b/fonts.js @@ -855,8 +855,6 @@ var Font = (function () { var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - console.log("added font", fontName); - console.log(rule); } } }; diff --git a/pdf.js b/pdf.js index 64b99a33e..273550084 100644 --- a/pdf.js +++ b/pdf.js @@ -2269,14 +2269,32 @@ var Encodings = { } }; +function ImageCanvas(width, height) { + var tmpCanvas = this.canvas = document.createElement("canvas"); + tmpCanvas.width = width; + tmpCanvas.height = height; + + this.ctx = tmpCanvas.getContext("2d"); + this.imgData = this.ctx.getImageData(0, 0, width, height); +} + +ImageCanvas.prototype.putImageData = function(imgData) { + this.ctx.putImageData(imgData, 0, 0); +} + +ImageCanvas.prototype.getCanvas = function() { + return this.canvas; +} + var CanvasGraphics = (function() { - function constructor(canvasCtx) { + function constructor(canvasCtx, imageCanvas) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); this.stateStack = [ ]; this.pendingClip = null; this.res = null; this.xobjs = null; + this.ImageCanvas = imageCanvas || ImageCanvas; } constructor.prototype = { @@ -3009,6 +3027,7 @@ var CanvasGraphics = (function() { var tmpCanvas = document.createElement("canvas"); tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]); tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]); + console.log("tilingFill", tmpCanvas.width, tmpCanvas.height); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); @@ -3249,6 +3268,7 @@ var CanvasGraphics = (function() { ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h); this.restore(); + console.log("drawImage"); return; } @@ -3328,11 +3348,15 @@ var CanvasGraphics = (function() { // handle matte object } - var tmpCanvas = document.createElement("canvas"); - tmpCanvas.width = w; - tmpCanvas.height = h; - var tmpCtx = tmpCanvas.getContext("2d"); - var imgData = tmpCtx.getImageData(0, 0, w, h); + var tmpCanvas = new this.ImageCanvas(w, h); + // var tmpCanvas = document.createElement("canvas"); + // tmpCanvas.width = w; + // tmpCanvas.height = h; + // + // var tmpCtx = tmpCanvas.getContext("2d"); + // var imgData = tmpCtx.getImageData(0, 0, w, h); + // var pixels = imgData.data; + var imgData = tmpCanvas.imgData; var pixels = imgData.data; if (bitsPerComponent != 8) @@ -3399,8 +3423,9 @@ var CanvasGraphics = (function() { TODO("Images with "+ numComps + " components per pixel"); } } - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); + console.log("paintImageXObject", w, h); + tmpCanvas.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas.getCanvas(), 0, -h); this.restore(); }, From 39ac389a7e556081f6c47f5b17c6a1453f3f680b Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 10:40:51 +0200 Subject: [PATCH 16/45] Get working for not real images --- canvas_proxy.js | 29 +++++++++++++++++++++++++++++ viewer_worker.html | 15 ++++++++++----- worker.js | 37 +++---------------------------------- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index ccc95c74a..610cdcdba 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,3 +1,24 @@ +var ImageCanvasProxyCounter = 0; +function ImageCanvasProxy(width, height) { + this.id = ImageCanvasProxyCounter++; + this.width = width; + this.height = height; + + // Using `Uint8ClampedArray` seems to be the type of ImageData - at least + // Firebug tells me so. + this.imgData = { + data: Uint8ClampedArray(width * height * 4) + }; +} + +ImageCanvasProxy.prototype.putImageData = function(imgData) { + // this.ctx.putImageData(imgData, 0, 0); +} + +ImageCanvasProxy.prototype.getCanvas = function() { + return this; +} + function CanvasProxy(width, height) { var stack = this.$stack = []; @@ -51,6 +72,14 @@ function CanvasProxy(width, height) { "$showText" ]; + this.drawImage = function(canvas, x, y) { + if (canvas instanceof ImageCanvasProxy) { + stack.push(["$drawCanvas", [canvas.imgData, x, y, canvas.width, canvas.height]]); + } else { + throw "unkown type to drawImage"; + } + } + function buildFuncCall(name) { return function() { // console.log("funcCall", name) diff --git a/viewer_worker.html b/viewer_worker.html index 930fb6cd5..07623c50c 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -4,11 +4,6 @@ <script> var myWorker = new Worker('worker.js'); -// var array = new Uint8Array(2); -// array[0] = 1; -// array[1] = 300; -// - var currentX = 0; var currentXStack = []; var special = { @@ -34,6 +29,16 @@ var special = { this.translate(currentX, -1 * y); this.fillText(text, 0, 0); currentX += this.measureText(text).width; + }, + + "$drawCanvas": function(data, x, y, width, height) { + // Ugly: getImageData is called here only to get an object of the right + // shape - we are not interessted in the data, as we set it the line + // afterwards to something custome. + // Can we do better here? + var imgData = ctx.getImageData(0, 0, width, height); + imgData.data = data; + ctx.putImageData(imgData, x, y); } } diff --git a/worker.js b/worker.js index 6d34a9d62..33b34f350 100644 --- a/worker.js +++ b/worker.js @@ -41,6 +41,7 @@ var canvas = new CanvasProxy(1224, 1584); log("test"); var pageInterval; + onmessage = function(event) { var data = event.data; var pdfDocument = new PDFDoc(new Stream(data)); @@ -48,48 +49,16 @@ onmessage = function(event) { tic(); // Let's try to render the first page... - var page = pdfDocument.getPage(8); + var page = pdfDocument.getPage(2); // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display var fonts = []; - var gfx = new CanvasGraphics(canvas); + var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); page.compile(gfx, fonts); toc("compiled page"); - // - var fontsReady = true; - // Inspect fonts and translate the missing one - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - fontsReady = false; - } - - // function delayLoadFont() { - // for (var i = 0; i < count; i++) { - // if (Fonts[font.name].loading) - // return; - // } - // clearInterval(pageInterval); - // page.display(gfx); - // - // log("flush"); - // canvas.flush(); - // }; - - // if (fontsReady) { - // delayLoadFont(); - // } else { - // pageInterval = setInterval(delayLoadFont, 10); - // } page.display(gfx); canvas.flush(); From 52a117d32a1ff11f9e6810053a2988af90f62e58 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 11:08:30 +0200 Subject: [PATCH 17/45] Fixing some smaller bugs & do some cleanup. viewer_worker.html UI is now --- pdf.js | 3 +- viewer_worker.html | 84 +++++++++++++++++++++++++++++++--------------- worker.js | 77 ++++++++++++++++++++---------------------- 3 files changed, 94 insertions(+), 70 deletions(-) diff --git a/pdf.js b/pdf.js index 273550084..61ceb602a 100644 --- a/pdf.js +++ b/pdf.js @@ -2867,7 +2867,7 @@ var CanvasGraphics = (function() { this.ctx.scale(1, -1); if (this.ctx.$showText) { - this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); + this.ctx.$showText(this.current.y, text, Fonts.charsToUnicode(text)); } else { text = Fonts.charsToUnicode(text); this.ctx.translate(this.current.x, -1 * this.current.y); @@ -3423,7 +3423,6 @@ var CanvasGraphics = (function() { TODO("Images with "+ numComps + " components per pixel"); } } - console.log("paintImageXObject", w, h); tmpCanvas.putImageData(imgData, 0, 0); ctx.drawImage(tmpCanvas.getCanvas(), 0, -h); this.restore(); diff --git a/viewer_worker.html b/viewer_worker.html index 07623c50c..bba694f21 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -2,6 +2,16 @@ <head> <title>Simple pdf.js page viewer worker</title> <script> + +var timer = null +function tic() { + timer = Date.now(); +} + +function toc(msg) { + console.log(msg + ": " + (Date.now() - timer) + "ms"); +} + var myWorker = new Worker('worker.js'); var currentX = 0; @@ -23,11 +33,9 @@ var special = { currentX = currentXStack.pop(); }, - "$showText": function(y, text) { - // console.log(text, currentX, y, this.measureText(text).width); - + "$showText": function(y, text, uniText) { this.translate(currentX, -1 * y); - this.fillText(text, 0, 0); + this.fillText(uniText, 0, 0); currentX += this.measureText(text).width; }, @@ -48,20 +56,10 @@ function renderProxyCanvas(stack) { // for (var i = 0; i < 1000; i++) { var opp = stack[i]; if (opp[0] == "$") { - // console.log("set property", opp[1], opp[2]); - if (opp[1] == "font") { - ctx[opp[1]] = opp[2]; - // ctx.font = "10px 'Verdana Bold Italic'"; - // console.log("font", opp[2]); - } else { - ctx[opp[1]] = opp[2]; - } - + ctx[opp[1]] = opp[2]; } else if (opp[0] in special) { - // console.log("sepcial", opp[0], opp[1]) special[opp[0]].apply(ctx, opp[1]); } else { - // console.log("execute", opp[0], opp[1]); ctx[opp[0]].apply(ctx, opp[1]); } } @@ -71,6 +69,7 @@ const WAIT = 0; const CANVAS_PROXY_STACK = 1; const LOG = 2; const FONT = 3; +const PDF_NUM_PAGE = 4; var onMessageState = WAIT; var fontStr = null; @@ -85,6 +84,9 @@ myWorker.onmessage = function(event) { throw "expecting to get an string"; } switch (data) { + case "pdf_num_page": + onMessageState = PDF_NUM_PAGE; + return; case "log": onMessageState = LOG; return; @@ -99,6 +101,13 @@ myWorker.onmessage = function(event) { } break; + case PDF_NUM_PAGE: + console.log(data); + maxPages = parseInt(data); + document.getElementById("numPages").innerHTML = "/" + data; + onMessageState = WAIT; + break; + case FONT: data = JSON.parse(data); var base64 = window.btoa(data.str); @@ -115,6 +124,7 @@ myWorker.onmessage = function(event) { ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial"; }, 10); intervals.push(interval); + console.log("setup font", data.fontName); onMessageState = WAIT; break; @@ -127,7 +137,7 @@ myWorker.onmessage = function(event) { case CANVAS_PROXY_STACK: var stack = JSON.parse(data); gStack = stack; - console.log("canvas stack", stack.length) + console.log("canvas stack size", stack.length) // Shedule a timeout. Hoping the fonts are loaded after 100ms. setTimeout(function() { @@ -135,7 +145,9 @@ myWorker.onmessage = function(event) { intervals.forEach(function(inter) { clearInterval(inter); }); + tic(); renderProxyCanvas(stack); + toc("canvas rendering") }, 100); onMessageState = WAIT; break; @@ -144,6 +156,19 @@ myWorker.onmessage = function(event) { // // myWorker.postMessage(array); +var currentPage = 1; +var maxPages = 1; +function showPage(num) { + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + console.log("worker: page=" + num) + document.getElementById('pageNumber').value = num; + currentPage = parseInt(num); + myWorker.postMessage(num); +} + function open(url) { document.title = url; var req = new XMLHttpRequest(); @@ -155,22 +180,27 @@ function open(url) { var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; myWorker.postMessage(data); + showPage("1"); } }; req.send(null); } +function nextPage() { + if (currentPage == maxPages) return; + currentPage++; + showPage(currentPage); +} + +function prevPage() { + if (currentPage == 0) return; + currentPage--; + showPage(currentPage); +} + window.onload = function() { - var ctx = window.ctx = document.getElementById("canvas").getContext("2d"); - ctx.save(); - ctx.fillStyle = "rgb(255, 255, 255)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - // for (var name in ctx) { - // if (!(ctx[name] instanceof Function)) { - // console.log('"' + name + '": "' + ctx[name] + '",'); - // } - // } + window.canvas = document.getElementById("canvas"); + window.ctx = canvas.getContext("2d"); open("compressed.tracemonkey-pldi-09.pdf"); } </script> @@ -184,7 +214,7 @@ window.onload = function() { -- Can we use JSONP to overcome the same-origin restrictions? --> <button onclick="prevPage();">Previous</button> <button onclick="nextPage();">Next</button> - <input type="text" id="pageNumber" onchange="gotoPage(this.value);" + <input type="text" id="pageNumber" onchange="showPage(this.value);" value="1" size="4"></input> <span id="numPages">--</span> <span id="info"></span> diff --git a/worker.js b/worker.js index 33b34f350..dcb87a811 100644 --- a/worker.js +++ b/worker.js @@ -26,7 +26,7 @@ function tic() { } function toc(msg) { - log("Took ", (Date.now() - timer)); + log(msg + ": " + (Date.now() - timer) + "ms"); timer = null; } @@ -41,46 +41,41 @@ var canvas = new CanvasProxy(1224, 1584); log("test"); var pageInterval; - +var pdfDocument = null; onmessage = function(event) { var data = event.data; - var pdfDocument = new PDFDoc(new Stream(data)); - var numPages = pdfDocument.numPages; - - tic(); - // Let's try to render the first page... - var page = pdfDocument.getPage(2); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - - var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); - page.compile(gfx, fonts); - toc("compiled page"); - - - page.display(gfx); - canvas.flush(); + if (!pdfDocument) { + pdfDocument = new PDFDoc(new Stream(data)); + postMessage("pdf_num_page"); + postMessage(pdfDocument.numPages) + return; + } else { + tic(); + + // Let's try to render the first page... + var page = pdfDocument.getPage(parseInt(data)); + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); + page.compile(gfx, fonts); + + // Inspect fonts and translate the missing one. + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + + // This "builds" the font and sents it over to the main thread. + new Font(font.name, font.file, font.properties); + } + toc("compiled page"); + + page.display(gfx); + canvas.flush(); + } } - -// function open(url) { -// var req = new XMLHttpRequest(); -// req.open("GET", url); -// // req.responseType = "arraybuffer"; -// req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200; -// req.onreadystatechange = function() { -// postMessage("loaded"); -// if (req.readyState == 4 && req.status == req.expected) { -// var data = req.mozResponseArrayBuffer || req.mozResponse || -// req.responseArrayBuffer || req.response; -// pdfDocument = new PDFDoc(new Stream(data)); -// numPages = pdfDocument.numPages; -// // document.getElementById("numPages").innerHTML = numPages.toString(); -// // goToPage(pageNum); -// } -// }; -// req.send(null); -// } -// -// open("compressed.tracemonkey-pldi-09.pdf") \ No newline at end of file From e2cd5facbda0bf851126d7782f9b64e200ef8e59 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 13:19:25 +0200 Subject: [PATCH 18/45] Fix font loading issue by using a hidden DOM font node --- viewer_worker.html | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/viewer_worker.html b/viewer_worker.html index bba694f21..ced71679e 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -118,12 +118,10 @@ myWorker.onmessage = function(event) { var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - // *HACK*: this makes the font get loaded on the page. WTF? We - // really have to set the fonts a few time... - var interval = setInterval(function() { - ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial"; - }, 10); - intervals.push(interval); + // Just adding the font-face to the DOM doesn't make it load. It + // seems it's loaded once Gecko notices it's used. Therefore, + // add a div on the page using the loaded font. + document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; console.log("setup font", data.fontName); onMessageState = WAIT; @@ -139,16 +137,13 @@ myWorker.onmessage = function(event) { gStack = stack; console.log("canvas stack size", stack.length) - // Shedule a timeout. Hoping the fonts are loaded after 100ms. + // There might be fonts that need to get loaded. Shedule the + // rendering at the end of the event queue ensures this. setTimeout(function() { - // Remove all setIntervals to make the font load. - intervals.forEach(function(inter) { - clearInterval(inter); - }); tic(); renderProxyCanvas(stack); toc("canvas rendering") - }, 100); + }, 0); onMessageState = WAIT; break; } @@ -208,6 +203,7 @@ window.onload = function() { </head> <body> + <div id="fonts" style="position: absolute;display:block;z-index:-1;"></div> <div id="controls"> <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input> <!-- This only opens supported PDFs from the source path... From 2c17af3720dac89b559f6333e99ab0afcd5ce0cf Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 14:02:54 +0200 Subject: [PATCH 19/45] Add JpegStreamProxy - doesnt seem to be used anywhere in the example.pdf file --- canvas_proxy.js | 44 ++++++++++++++++++++++++++++++++++++++------ viewer_worker.html | 25 +++++++++++++++++++++++-- worker.js | 3 +++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 610cdcdba..c7234d9c9 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -19,6 +19,38 @@ ImageCanvasProxy.prototype.getCanvas = function() { return this; } +var JpegStreamProxyCounter = 0; +// WebWorker Proxy for JpegStream. +var JpegStreamProxy = (function() { + function constructor(bytes, dict) { + this.id = JpegStreamProxyCounter++; + this.dict = dict; + + // create DOM image. + postMessage("jpeg_stream"); + postMessage({ + id: this.id, + str: bytesToString(bytes) + }); + + // var img = new Image(); + // img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes)); + // this.domImage = img; + } + + constructor.prototype = { + getImage: function() { + return this; + // return this.domImage; + }, + getChar: function() { + error("internal error: getChar is not valid on JpegStream"); + } + }; + + return constructor; +})(); + function CanvasProxy(width, height) { var stack = this.$stack = []; @@ -72,9 +104,11 @@ function CanvasProxy(width, height) { "$showText" ]; - this.drawImage = function(canvas, x, y) { - if (canvas instanceof ImageCanvasProxy) { - stack.push(["$drawCanvas", [canvas.imgData, x, y, canvas.width, canvas.height]]); + this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { + if (image instanceof ImageCanvasProxy) { + stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]); + } else if(image instanceof JpegStreamProxy) { + stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) } else { throw "unkown type to drawImage"; } @@ -139,9 +173,7 @@ function CanvasProxy(width, height) { } CanvasProxy.prototype.flush = function() { - // postMessage("log"); - // postMessage(JSON.stringify([this.$stack.length])); postMessage("canvas_proxy_stack"); - postMessage(JSON.stringify(this.$stack)); + postMessage(this.$stack); this.$stack.length = 0; } diff --git a/viewer_worker.html b/viewer_worker.html index ced71679e..fd436db75 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -13,6 +13,7 @@ function toc(msg) { } var myWorker = new Worker('worker.js'); +var images = {}; var currentX = 0; var currentXStack = []; @@ -47,6 +48,15 @@ var special = { var imgData = ctx.getImageData(0, 0, width, height); imgData.data = data; ctx.putImageData(imgData, x, y); + }, + + "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { + var image = images[id]; + if (!image) { + throw "Image not found"; + } + ctx.drawImage(image, x, y, image.width, image.height, + sx, sy, swidth, sheight); } } @@ -70,6 +80,7 @@ const CANVAS_PROXY_STACK = 1; const LOG = 2; const FONT = 3; const PDF_NUM_PAGE = 4; +const JPEG_STREAM = 5; var onMessageState = WAIT; var fontStr = null; @@ -96,11 +107,21 @@ myWorker.onmessage = function(event) { case "font": onMessageState = FONT; return; + case "jpeg_stream": + onMessageState = JPEG_STREAM; + return; default: throw "unkown state: " + data } break; + case JPEG_STREAM: + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(data.str); + images[data.id] = img; + console.log("got image", data.id) + break; + case PDF_NUM_PAGE: console.log(data); maxPages = parseInt(data); @@ -133,7 +154,7 @@ myWorker.onmessage = function(event) { break; case CANVAS_PROXY_STACK: - var stack = JSON.parse(data); + var stack = data; gStack = stack; console.log("canvas stack size", stack.length) @@ -188,7 +209,7 @@ function nextPage() { } function prevPage() { - if (currentPage == 0) return; + if (currentPage == 1) return; currentPage--; showPage(currentPage); } diff --git a/worker.js b/worker.js index dcb87a811..6294007b6 100644 --- a/worker.js +++ b/worker.js @@ -15,6 +15,9 @@ importScripts("pdf.js"); importScripts("fonts.js"); importScripts("glyphlist.js") +// Use the JpegStreamProxy proxy. +JpegStream = JpegStreamProxy; + // var array = new Uint8Array(2); // array[0] = 1; // array[1] = 300; From fe7c8b52f645e60c5569f319fa52e074813d53c7 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 14:25:21 +0200 Subject: [PATCH 20/45] Add very simple GradientProxy support - makes page 11 render. --- canvas_proxy.js | 35 +++++++++++++++++++++++++++++++++-- viewer_worker.html | 19 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index c7234d9c9..0670762e5 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -51,6 +51,16 @@ var JpegStreamProxy = (function() { return constructor; })(); +// Really simple GradientProxy. There is currently only one active gradient at +// the time, meaning you can't create a gradient, create a second one and then +// use the first one again. As this isn't used in pdf.js right now, it's okay. +function GradientProxy(stack, x0, y0, x1, y1) { + stack.push(["$createLinearGradient", [x0, y0, x1, y1]]); + this.addColorStop = function(i, rgba) { + stack.push(["$addColorStop", [i, rgba]]); + } +} + function CanvasProxy(width, height) { var stack = this.$stack = []; @@ -79,7 +89,7 @@ function CanvasProxy(width, height) { "translate", "transform", "setTransform", - "createLinearGradient", + // "createLinearGradient", "createPattern", "clearRect", "fillRect", @@ -104,6 +114,10 @@ function CanvasProxy(width, height) { "$showText" ]; + this.createLinearGradient = function(x0, y0, x1, y1) { + return new GradientProxy(stack, x0, y0, x1, y1); + } + this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { if (image instanceof ImageCanvasProxy) { stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]); @@ -168,7 +182,24 @@ function CanvasProxy(width, height) { for (var name in ctxProp) { this["$" + name] = ctxProp[name]; this.__defineGetter__(name, buildGetter(name)); - this.__defineSetter__(name, buildSetter(name)); + + // Special treatment for `fillStyle` and `strokeStyle`: The passed style + // might be a gradient. Need to check for that. + if (name == "fillStyle" || name == "strokeStyle") { + function buildSetterStyle(name) { + return function(value) { + if (value instanceof GradientProxy) { + stack.push(["$" + name + "Gradient"]); + } else { + stack.push(["$", name, value]); + return this["$" + name] = value; + } + } + } + this.__defineSetter__(name, buildSetterStyle(name)); + } else { + this.__defineSetter__(name, buildSetter(name)); + } } } diff --git a/viewer_worker.html b/viewer_worker.html index fd436db75..83c41e6e0 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -14,6 +14,7 @@ function toc(msg) { var myWorker = new Worker('worker.js'); var images = {}; +var gradient; var currentX = 0; var currentXStack = []; @@ -57,6 +58,22 @@ var special = { } ctx.drawImage(image, x, y, image.width, image.height, sx, sy, swidth, sheight); + }, + + "$createLinearGradient": function(x0, y0, x1, y1) { + gradient = ctx.createLinearGradient(x0, y0, x1, y1); + }, + + "$addColorStop": function(i, rgba) { + gradient.addColorStop(i, rgba); + }, + + "$fillStyleGradient": function() { + ctx.fillStyle = gradient; + }, + + "$strokeStyleGradient": function() { + ctx.strokeStyle = gradient; } } @@ -196,7 +213,7 @@ function open(url) { var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; myWorker.postMessage(data); - showPage("1"); + showPage("11"); } }; req.send(null); From 50d902d9eb41f5aee01bdd36f5ffdaf169dd01c9 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 20:04:25 +0200 Subject: [PATCH 21/45] Make the ProxyCanvas be more a canvas - provide a ctx object to interact through --- canvas_proxy.js | 31 ++++++++++++++++++++----------- worker.js | 16 ++-------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 0670762e5..823757492 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -64,9 +64,18 @@ function GradientProxy(stack, x0, y0, x1, y1) { function CanvasProxy(width, height) { var stack = this.$stack = []; + // Dummy context exposed. + var ctx = {}; + this.getContext = function(type) { + if (type != "2d") { + throw "CanvasProxy can only provide a 2d context."; + } + return ctx; + } + // Expose only the minimum of the canvas object - there is no dom to do // more here. - this.canvas = { + ctx.canvas = { width: width, height: height } @@ -114,11 +123,11 @@ function CanvasProxy(width, height) { "$showText" ]; - this.createLinearGradient = function(x0, y0, x1, y1) { + ctx.createLinearGradient = function(x0, y0, x1, y1) { return new GradientProxy(stack, x0, y0, x1, y1); } - this.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { + ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { if (image instanceof ImageCanvasProxy) { stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]); } else if(image instanceof JpegStreamProxy) { @@ -137,7 +146,7 @@ function CanvasProxy(width, height) { var name; for (var i = 0; i < ctxFunc.length; i++) { name = ctxFunc[i]; - this[name] = buildFuncCall(name); + ctx[name] = buildFuncCall(name); } var ctxProp = { @@ -168,20 +177,20 @@ function CanvasProxy(width, height) { function buildGetter(name) { return function() { - return this["$" + name]; + return ctx["$" + name]; } } function buildSetter(name) { return function(value) { stack.push(["$", name, value]); - return this["$" + name] = value; + return ctx["$" + name] = value; } } for (var name in ctxProp) { - this["$" + name] = ctxProp[name]; - this.__defineGetter__(name, buildGetter(name)); + ctx["$" + name] = ctxProp[name]; + ctx.__defineGetter__(name, buildGetter(name)); // Special treatment for `fillStyle` and `strokeStyle`: The passed style // might be a gradient. Need to check for that. @@ -192,13 +201,13 @@ function CanvasProxy(width, height) { stack.push(["$" + name + "Gradient"]); } else { stack.push(["$", name, value]); - return this["$" + name] = value; + return ctx["$" + name] = value; } } } - this.__defineSetter__(name, buildSetterStyle(name)); + ctx.__defineSetter__(name, buildSetterStyle(name)); } else { - this.__defineSetter__(name, buildSetter(name)); + ctx.__defineSetter__(name, buildSetter(name)); } } } diff --git a/worker.js b/worker.js index 6294007b6..59ad8edea 100644 --- a/worker.js +++ b/worker.js @@ -18,11 +18,6 @@ importScripts("glyphlist.js") // Use the JpegStreamProxy proxy. JpegStream = JpegStreamProxy; -// var array = new Uint8Array(2); -// array[0] = 1; -// array[1] = 300; -// postMessage(array); - var timer = null; function tic() { timer = Date.now(); @@ -33,15 +28,8 @@ function toc(msg) { timer = null; } - +// Create the WebWorkerProxyCanvas. var canvas = new CanvasProxy(1224, 1584); -// canvas.moveTo(0, 10); -// canvas.lineTo(0, 20); -// canvas.lineTo(500, 500); -// canvas.flush(); -// canvas.stroke(); -// canvas.flush(); -log("test"); var pageInterval; var pdfDocument = null; @@ -61,7 +49,7 @@ onmessage = function(event) { // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display var fonts = []; - var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); + var gfx = new CanvasGraphics(canvas.getContext("2d"), ImageCanvasProxy); page.compile(gfx, fonts); // Inspect fonts and translate the missing one. From 000d6402580dcd99efdca83031ac2e3b1055bd31 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 20:16:04 +0200 Subject: [PATCH 22/45] Replace ImageCanvas by ScratchCanvas --- pdf.js | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/pdf.js b/pdf.js index 61ceb602a..221fd5ece 100644 --- a/pdf.js +++ b/pdf.js @@ -2269,21 +2269,18 @@ var Encodings = { } }; -function ImageCanvas(width, height) { - var tmpCanvas = this.canvas = document.createElement("canvas"); - tmpCanvas.width = width; - tmpCanvas.height = height; +function ScratchCanvas(width, height) { + var canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; - this.ctx = tmpCanvas.getContext("2d"); - this.imgData = this.ctx.getImageData(0, 0, width, height); -} - -ImageCanvas.prototype.putImageData = function(imgData) { - this.ctx.putImageData(imgData, 0, 0); -} + this.getContext = function(kind) { + return canvas.getContext(kind); + } -ImageCanvas.prototype.getCanvas = function() { - return this.canvas; + this.getCanvas = function() { + return canvas; + } } var CanvasGraphics = (function() { @@ -2294,7 +2291,7 @@ var CanvasGraphics = (function() { this.pendingClip = null; this.res = null; this.xobjs = null; - this.ImageCanvas = imageCanvas || ImageCanvas; + this.ScratchCanvas = imageCanvas || ScratchCanvas; } constructor.prototype = { @@ -3348,15 +3345,9 @@ var CanvasGraphics = (function() { // handle matte object } - var tmpCanvas = new this.ImageCanvas(w, h); - // var tmpCanvas = document.createElement("canvas"); - // tmpCanvas.width = w; - // tmpCanvas.height = h; - // - // var tmpCtx = tmpCanvas.getContext("2d"); - // var imgData = tmpCtx.getImageData(0, 0, w, h); - // var pixels = imgData.data; - var imgData = tmpCanvas.imgData; + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext("2d"); + var imgData = tmpCtx.getImageData(0, 0, w, h); var pixels = imgData.data; if (bitsPerComponent != 8) @@ -3423,7 +3414,7 @@ var CanvasGraphics = (function() { TODO("Images with "+ numComps + " components per pixel"); } } - tmpCanvas.putImageData(imgData, 0, 0); + tmpCtx.putImageData(imgData, 0, 0); ctx.drawImage(tmpCanvas.getCanvas(), 0, -h); this.restore(); }, From 22bdd50a98e3097c98288b116618543550a7387b Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 20:49:33 +0200 Subject: [PATCH 23/45] Merge ImageCanvasProxy and CanvasProxy. Add support for rendering multiple canvas objects on the worker and assemble them again on the main thread. --- canvas_proxy.js | 79 ++++++++++++++++++++++++++++++---------------- viewer_worker.html | 59 ++++++++++++++++++++++++---------- worker.js | 2 +- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 823757492..ed209f126 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,23 +1,23 @@ -var ImageCanvasProxyCounter = 0; -function ImageCanvasProxy(width, height) { - this.id = ImageCanvasProxyCounter++; - this.width = width; - this.height = height; - - // Using `Uint8ClampedArray` seems to be the type of ImageData - at least - // Firebug tells me so. - this.imgData = { - data: Uint8ClampedArray(width * height * 4) - }; -} - -ImageCanvasProxy.prototype.putImageData = function(imgData) { - // this.ctx.putImageData(imgData, 0, 0); -} - -ImageCanvasProxy.prototype.getCanvas = function() { - return this; -} +// var ImageCanvasProxyCounter = 0; +// function ImageCanvasProxy(width, height) { +// this.id = ImageCanvasProxyCounter++; +// this.width = width; +// this.height = height; +// +// // Using `Uint8ClampedArray` seems to be the type of ImageData - at least +// // Firebug tells me so. +// this.imgData = { +// data: Uint8ClampedArray(width * height * 4) +// }; +// } +// +// ImageCanvasProxy.prototype.putImageData = function(imgData) { +// // this.ctx.putImageData(imgData, 0, 0); +// } +// +// ImageCanvasProxy.prototype.getCanvas = function() { +// return this; +// } var JpegStreamProxyCounter = 0; // WebWorker Proxy for JpegStream. @@ -61,7 +61,10 @@ function GradientProxy(stack, x0, y0, x1, y1) { } } +var canvasProxyCounter = 0; function CanvasProxy(width, height) { + this.id = canvasProxyCounter++; + var stack = this.$stack = []; // Dummy context exposed. @@ -73,12 +76,15 @@ function CanvasProxy(width, height) { return ctx; } + this.getCanvas = function() { + return this; + } + // Expose only the minimum of the canvas object - there is no dom to do // more here. - ctx.canvas = { - width: width, - height: height - } + this.width = width; + this.height = height; + ctx.canvas = this; var ctxFunc = [ "createRadialGradient", @@ -127,9 +133,23 @@ function CanvasProxy(width, height) { return new GradientProxy(stack, x0, y0, x1, y1); } + ctx.getImageData = function(x, y, w, h) { + return { + width: w, + height: h, + data: Uint8ClampedArray(w * h * 4) + }; + } + + ctx.putImageData = function(data, x, y, width, height) { + stack.push(["$putImageData", [data, x, y, width, height]]); + } + ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { - if (image instanceof ImageCanvasProxy) { - stack.push(["$drawCanvas", [image.imgData, x, y, image.width, image.height]]); + if (image instanceof CanvasProxy) { + // Send the image/CanvasProxy to the main thread. + image.flush(); + stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); } else if(image instanceof JpegStreamProxy) { stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) } else { @@ -214,6 +234,11 @@ function CanvasProxy(width, height) { CanvasProxy.prototype.flush = function() { postMessage("canvas_proxy_stack"); - postMessage(this.$stack); + postMessage({ + id: this.id, + stack: this.$stack, + width: this.width, + height: this.height + }); this.$stack.length = 0; } diff --git a/viewer_worker.html b/viewer_worker.html index 83c41e6e0..c7041bcbd 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -13,7 +13,8 @@ function toc(msg) { } var myWorker = new Worker('worker.js'); -var images = {}; +var imagesList = {}; +var canvasList = {}; var gradient; var currentX = 0; @@ -41,27 +42,40 @@ var special = { currentX += this.measureText(text).width; }, - "$drawCanvas": function(data, x, y, width, height) { + "$putImageData": function(imageData, x, y) { // Ugly: getImageData is called here only to get an object of the right // shape - we are not interessted in the data, as we set it the line // afterwards to something custome. // Can we do better here? - var imgData = ctx.getImageData(0, 0, width, height); - imgData.data = data; - ctx.putImageData(imgData, x, y); + var imgData = this.getImageData(0, 0, imageData.width, imageData.height); + imgData.data = imageData.data; + this.putImageData(imgData, x, y); }, "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { - var image = images[id]; + var image = imagesList[id]; if (!image) { throw "Image not found"; } - ctx.drawImage(image, x, y, image.width, image.height, + this.drawImage(image, x, y, image.width, image.height, sx, sy, swidth, sheight); }, + "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) { + var canvas = canvasList[id]; + if (!canvas) { + throw "Canvas not found"; + } + if (sheight != null) { + this.drawImage(canvas, x, y, canvas.width, canvas.height, + sx, sy, swidth, sheight); + } else { + this.drawImage(canvas, x, y, canvas.width, canvas.height); + } + }, + "$createLinearGradient": function(x0, y0, x1, y1) { - gradient = ctx.createLinearGradient(x0, y0, x1, y1); + gradient = this.createLinearGradient(x0, y0, x1, y1); }, "$addColorStop": function(i, rgba) { @@ -69,16 +83,17 @@ var special = { }, "$fillStyleGradient": function() { - ctx.fillStyle = gradient; + this.fillStyle = gradient; }, "$strokeStyleGradient": function() { - ctx.strokeStyle = gradient; + this.strokeStyle = gradient; } } var gStack; -function renderProxyCanvas(stack) { +function renderProxyCanvas(canvas, stack) { + var ctx = canvas.getContext("2d"); for (var i = 0; i < stack.length; i++) { // for (var i = 0; i < 1000; i++) { var opp = stack[i]; @@ -135,7 +150,7 @@ myWorker.onmessage = function(event) { case JPEG_STREAM: var img = new Image(); img.src = "data:image/jpeg;base64," + window.btoa(data.str); - images[data.id] = img; + imagesList[data.id] = img; console.log("got image", data.id) break; @@ -171,16 +186,25 @@ myWorker.onmessage = function(event) { break; case CANVAS_PROXY_STACK: - var stack = data; + var id = data.id; + var stack = data.stack; gStack = stack; - console.log("canvas stack size", stack.length) + + // Check if there is already a canvas with the given id. If not, + // create a new canvas. + if (!canvasList[id]) { + var newCanvas = document.createElement("canvas"); + newCanvas.width = data.width; + newCanvas.height = data.height; + canvasList[id] = newCanvas; + } // There might be fonts that need to get loaded. Shedule the // rendering at the end of the event queue ensures this. setTimeout(function() { - tic(); - renderProxyCanvas(stack); - toc("canvas rendering") + if (id == 0) tic(); + renderProxyCanvas(canvasList[id], stack); + if (id == 0) toc("canvas rendering") }, 0); onMessageState = WAIT; break; @@ -234,6 +258,7 @@ function prevPage() { window.onload = function() { window.canvas = document.getElementById("canvas"); window.ctx = canvas.getContext("2d"); + canvasList[0] = window.canvas; open("compressed.tracemonkey-pldi-09.pdf"); } </script> diff --git a/worker.js b/worker.js index 59ad8edea..e59e37155 100644 --- a/worker.js +++ b/worker.js @@ -49,7 +49,7 @@ onmessage = function(event) { // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display var fonts = []; - var gfx = new CanvasGraphics(canvas.getContext("2d"), ImageCanvasProxy); + var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); page.compile(gfx, fonts); // Inspect fonts and translate the missing one. From 0746c7db59d7a4d2c4278573466a98a056e8d79a Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 21:17:32 +0200 Subject: [PATCH 24/45] Add PatternProxy; makes page 13 work --- canvas_proxy.js | 26 +++++++++++++++++++++----- pdf.js | 20 ++++++-------------- viewer_worker.html | 27 ++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index ed209f126..07ae31a63 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -61,6 +61,20 @@ function GradientProxy(stack, x0, y0, x1, y1) { } } +var patternProxyCounter = 0; +function PatternProxy(stack, object, kind) { + this.id = patternProxyCounter++; + + if (!(object instanceof CanvasProxy) ) { + throw "unkown type to createPattern"; + } + // Flush the object here to ensure it's available on the main thread. + // TODO: Make some kind of dependency management, such that the object + // gets flushed only if needed. + object.flush(); + stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); +} + var canvasProxyCounter = 0; function CanvasProxy(width, height) { this.id = canvasProxyCounter++; @@ -76,10 +90,6 @@ function CanvasProxy(width, height) { return ctx; } - this.getCanvas = function() { - return this; - } - // Expose only the minimum of the canvas object - there is no dom to do // more here. this.width = width; @@ -105,7 +115,7 @@ function CanvasProxy(width, height) { "transform", "setTransform", // "createLinearGradient", - "createPattern", + // "createPattern", "clearRect", "fillRect", "strokeRect", @@ -129,6 +139,10 @@ function CanvasProxy(width, height) { "$showText" ]; + ctx.createPattern = function(object, kind) { + return new PatternProxy(stack, object, kind); + } + ctx.createLinearGradient = function(x0, y0, x1, y1) { return new GradientProxy(stack, x0, y0, x1, y1); } @@ -219,6 +233,8 @@ function CanvasProxy(width, height) { return function(value) { if (value instanceof GradientProxy) { stack.push(["$" + name + "Gradient"]); + } else if (value instanceof PatternProxy) { + stack.push(["$" + name + "Pattern", [value.id]]); } else { stack.push(["$", name, value]); return ctx["$" + name] = value; diff --git a/pdf.js b/pdf.js index 221fd5ece..1223a2bb6 100644 --- a/pdf.js +++ b/pdf.js @@ -2273,14 +2273,7 @@ function ScratchCanvas(width, height) { var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; - - this.getContext = function(kind) { - return canvas.getContext(kind); - } - - this.getCanvas = function() { - return canvas; - } + return canvas; } var CanvasGraphics = (function() { @@ -3021,10 +3014,10 @@ var CanvasGraphics = (function() { // we want the canvas to be as large as the step size var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); - var tmpCanvas = document.createElement("canvas"); - tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]); - tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]); - console.log("tilingFill", tmpCanvas.width, tmpCanvas.height); + var tmpCanvas = new this.ScratchCanvas( + Math.ceil(botRight[0] - topLeft[0]), // WIDTH + Math.ceil(botRight[1] - topLeft[1]) // HEIGHT + ); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); @@ -3265,7 +3258,6 @@ var CanvasGraphics = (function() { ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h); this.restore(); - console.log("drawImage"); return; } @@ -3415,7 +3407,7 @@ var CanvasGraphics = (function() { } } tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas.getCanvas(), 0, -h); + ctx.drawImage(tmpCanvas, 0, -h); this.restore(); }, diff --git a/viewer_worker.html b/viewer_worker.html index c7041bcbd..f6d7f11e5 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -15,6 +15,7 @@ function toc(msg) { var myWorker = new Worker('worker.js'); var imagesList = {}; var canvasList = {}; +var patternList = {}; var gradient; var currentX = 0; @@ -78,6 +79,14 @@ var special = { gradient = this.createLinearGradient(x0, y0, x1, y1); }, + "$createPatternFromCanvas": function(patternId, canvasId, kind) { + var canvas = canvasList[canvasId]; + if (!canvas) { + throw "Canvas not found"; + } + patternList[patternId] = this.createPattern(canvas, kind); + }, + "$addColorStop": function(i, rgba) { gradient.addColorStop(i, rgba); }, @@ -86,8 +95,24 @@ var special = { this.fillStyle = gradient; }, + "$fillStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.fillStyle = pattern; + }, + "$strokeStyleGradient": function() { this.strokeStyle = gradient; + }, + + "$strokeStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.strokeStyle = pattern; } } @@ -237,7 +262,7 @@ function open(url) { var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; myWorker.postMessage(data); - showPage("11"); + showPage("13"); } }; req.send(null); From 08405fa618c55942c1d04b743f3dcbe198f186ce Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 22:00:53 +0200 Subject: [PATCH 25/45] Fix putImageData. Needs to be copied byte by byte. --- viewer_worker.html | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/viewer_worker.html b/viewer_worker.html index f6d7f11e5..92806bc99 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -44,12 +44,17 @@ var special = { }, "$putImageData": function(imageData, x, y) { - // Ugly: getImageData is called here only to get an object of the right - // shape - we are not interessted in the data, as we set it the line - // afterwards to something custome. - // Can we do better here? var imgData = this.getImageData(0, 0, imageData.width, imageData.height); - imgData.data = imageData.data; + + // Store the .data property to avaid property lookups. + var imageRealData = imageData.data; + var imgRealData = imgData.data; + + // Copy over the imageData. + var len = imageRealData.length; + while (len--) + imgRealData[len] = imageRealData[len] + this.putImageData(imgData, x, y); }, @@ -262,7 +267,7 @@ function open(url) { var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; myWorker.postMessage(data); - showPage("13"); + showPage("2"); } }; req.send(null); From 4281f799e5926d7087da69809d48ebe2ecfc7425 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Wed, 22 Jun 2011 22:54:16 +0200 Subject: [PATCH 26/45] Move client code into worker_client.js. Cleanup + comments + 2-space-indention --- canvas_proxy.js | 417 +++++++++++++++++++++------------------------ viewer_worker.html | 299 ++------------------------------ worker.js | 96 ++++++----- worker_client.js | 294 ++++++++++++++++++++++++++++++++ 4 files changed, 556 insertions(+), 550 deletions(-) create mode 100644 worker_client.js diff --git a/canvas_proxy.js b/canvas_proxy.js index 07ae31a63..83b57682f 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,260 +1,239 @@ -// var ImageCanvasProxyCounter = 0; -// function ImageCanvasProxy(width, height) { -// this.id = ImageCanvasProxyCounter++; -// this.width = width; -// this.height = height; -// -// // Using `Uint8ClampedArray` seems to be the type of ImageData - at least -// // Firebug tells me so. -// this.imgData = { -// data: Uint8ClampedArray(width * height * 4) -// }; -// } -// -// ImageCanvasProxy.prototype.putImageData = function(imgData) { -// // this.ctx.putImageData(imgData, 0, 0); -// } -// -// ImageCanvasProxy.prototype.getCanvas = function() { -// return this; -// } var JpegStreamProxyCounter = 0; // WebWorker Proxy for JpegStream. var JpegStreamProxy = (function() { - function constructor(bytes, dict) { - this.id = JpegStreamProxyCounter++; - this.dict = dict; + function constructor(bytes, dict) { + this.id = JpegStreamProxyCounter++; + this.dict = dict; - // create DOM image. - postMessage("jpeg_stream"); - postMessage({ - id: this.id, - str: bytesToString(bytes) - }); - - // var img = new Image(); - // img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes)); - // this.domImage = img; + // Tell the main thread to create an image. + postMessage("jpeg_stream"); + postMessage({ + id: this.id, + str: bytesToString(bytes) + }); + } + + constructor.prototype = { + getImage: function() { + return this; + }, + getChar: function() { + error("internal error: getChar is not valid on JpegStream"); } + }; - constructor.prototype = { - getImage: function() { - return this; - // return this.domImage; - }, - getChar: function() { - error("internal error: getChar is not valid on JpegStream"); - } - }; - - return constructor; + return constructor; })(); // Really simple GradientProxy. There is currently only one active gradient at // the time, meaning you can't create a gradient, create a second one and then // use the first one again. As this isn't used in pdf.js right now, it's okay. function GradientProxy(stack, x0, y0, x1, y1) { - stack.push(["$createLinearGradient", [x0, y0, x1, y1]]); - this.addColorStop = function(i, rgba) { - stack.push(["$addColorStop", [i, rgba]]); - } + stack.push(["$createLinearGradient", [x0, y0, x1, y1]]); + this.addColorStop = function(i, rgba) { + stack.push(["$addColorStop", [i, rgba]]); + } } +// Really simple PatternProxy. var patternProxyCounter = 0; function PatternProxy(stack, object, kind) { - this.id = patternProxyCounter++; + this.id = patternProxyCounter++; - if (!(object instanceof CanvasProxy) ) { - throw "unkown type to createPattern"; - } - // Flush the object here to ensure it's available on the main thread. - // TODO: Make some kind of dependency management, such that the object - // gets flushed only if needed. - object.flush(); - stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); + if (!(object instanceof CanvasProxy) ) { + throw "unkown type to createPattern"; + } + + // Flush the object here to ensure it's available on the main thread. + // TODO: Make some kind of dependency management, such that the object + // gets flushed only if needed. + object.flush(); + stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); } var canvasProxyCounter = 0; function CanvasProxy(width, height) { - this.id = canvasProxyCounter++; - - var stack = this.$stack = []; - - // Dummy context exposed. - var ctx = {}; - this.getContext = function(type) { - if (type != "2d") { - throw "CanvasProxy can only provide a 2d context."; - } - return ctx; - } - - // Expose only the minimum of the canvas object - there is no dom to do - // more here. - this.width = width; - this.height = height; - ctx.canvas = this; - - var ctxFunc = [ - "createRadialGradient", - "arcTo", - "arc", - "fillText", - "strokeText", - // "drawImage", - // "getImageData", - // "putImageData", - "createImageData", - "drawWindow", - "save", - "restore", - "scale", - "rotate", - "translate", - "transform", - "setTransform", - // "createLinearGradient", - // "createPattern", - "clearRect", - "fillRect", - "strokeRect", - "beginPath", - "closePath", - "moveTo", - "lineTo", - "quadraticCurveTo", - "bezierCurveTo", - "rect", - "fill", - "stroke", - "clip", - "measureText", - "isPointInPath", - - "$setCurrentX", - "$addCurrentX", - "$saveCurrentX", - "$restoreCurrentX", - "$showText" - ]; - - ctx.createPattern = function(object, kind) { - return new PatternProxy(stack, object, kind); - } + this.id = canvasProxyCounter++; - ctx.createLinearGradient = function(x0, y0, x1, y1) { - return new GradientProxy(stack, x0, y0, x1, y1); - } + // The `stack` holds the rendering calls and gets flushed to the main thead. + var stack = this.$stack = []; - ctx.getImageData = function(x, y, w, h) { - return { - width: w, - height: h, - data: Uint8ClampedArray(w * h * 4) - }; + // Dummy context that gets exposed. + var ctx = {}; + this.getContext = function(type) { + if (type != "2d") { + throw "CanvasProxy can only provide a 2d context."; } - - ctx.putImageData = function(data, x, y, width, height) { - stack.push(["$putImageData", [data, x, y, width, height]]); + return ctx; + } + + // Expose only the minimum of the canvas object - there is no dom to do + // more here. + this.width = width; + this.height = height; + ctx.canvas = this; + + // Setup function calls to `ctx`. + var ctxFunc = [ + "createRadialGradient", + "arcTo", + "arc", + "fillText", + "strokeText", + "createImageData", + "drawWindow", + "save", + "restore", + "scale", + "rotate", + "translate", + "transform", + "setTransform", + "clearRect", + "fillRect", + "strokeRect", + "beginPath", + "closePath", + "moveTo", + "lineTo", + "quadraticCurveTo", + "bezierCurveTo", + "rect", + "fill", + "stroke", + "clip", + "measureText", + "isPointInPath", + + // These functions are necessary to track the rendering currentX state. + // The exact values can be computed on the main thread only, as the + // worker has no idea about text width. + "$setCurrentX", + "$addCurrentX", + "$saveCurrentX", + "$restoreCurrentX", + "$showText" + ]; + + function buildFuncCall(name) { + return function() { + // console.log("funcCall", name) + stack.push([name, Array.prototype.slice.call(arguments)]); } - - ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { - if (image instanceof CanvasProxy) { - // Send the image/CanvasProxy to the main thread. - image.flush(); - stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); - } else if(image instanceof JpegStreamProxy) { - stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) - } else { - throw "unkown type to drawImage"; - } - } - - function buildFuncCall(name) { - return function() { - // console.log("funcCall", name) - stack.push([name, Array.prototype.slice.call(arguments)]); - } + } + var name; + for (var i = 0; i < ctxFunc.length; i++) { + name = ctxFunc[i]; + ctx[name] = buildFuncCall(name); + } + + // Some function calls that need more work. + + ctx.createPattern = function(object, kind) { + return new PatternProxy(stack, object, kind); + } + + ctx.createLinearGradient = function(x0, y0, x1, y1) { + return new GradientProxy(stack, x0, y0, x1, y1); + } + + ctx.getImageData = function(x, y, w, h) { + return { + width: w, + height: h, + data: Uint8ClampedArray(w * h * 4) + }; + } + + ctx.putImageData = function(data, x, y, width, height) { + stack.push(["$putImageData", [data, x, y, width, height]]); + } + + ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { + if (image instanceof CanvasProxy) { + // Send the image/CanvasProxy to the main thread. + image.flush(); + stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); + } else if(image instanceof JpegStreamProxy) { + stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) + } else { + throw "unkown type to drawImage"; } - var name; - for (var i = 0; i < ctxFunc.length; i++) { - name = ctxFunc[i]; - ctx[name] = buildFuncCall(name); + } + + // Setup property access to `ctx`. + var ctxProp = { + // "canvas" + "globalAlpha": "1", + "globalCompositeOperation": "source-over", + "strokeStyle": "#000000", + "fillStyle": "#000000", + "lineWidth": "1", + "lineCap": "butt", + "lineJoin": "miter", + "miterLimit": "10", + "shadowOffsetX": "0", + "shadowOffsetY": "0", + "shadowBlur": "0", + "shadowColor": "rgba(0, 0, 0, 0)", + "font": "10px sans-serif", + "textAlign": "start", + "textBaseline": "alphabetic", + "mozTextStyle": "10px sans-serif", + "mozImageSmoothingEnabled": "true" + } + + function buildGetter(name) { + return function() { + return ctx["$" + name]; } + } - var ctxProp = { - // "canvas" - "globalAlpha": "1", - "globalCompositeOperation": "source-over", - "strokeStyle": "#000000", - "fillStyle": "#000000", - "lineWidth": "1", - "lineCap": "butt", - "lineJoin": "miter", - "miterLimit": "10", - "shadowOffsetX": "0", - "shadowOffsetY": "0", - "shadowBlur": "0", - "shadowColor": "rgba(0, 0, 0, 0)", - "font": "10px sans-serif", - "textAlign": "start", - "textBaseline": "alphabetic", - "mozTextStyle": "10px sans-serif", - "mozImageSmoothingEnabled": "true", - "DRAWWINDOW_DRAW_CARET": "1", - "DRAWWINDOW_DO_NOT_FLUSH": "2", - "DRAWWINDOW_DRAW_VIEW": "4", - "DRAWWINDOW_USE_WIDGET_LAYERS": "8", - "DRAWWINDOW_ASYNC_DECODE_IMAGES": "16", + function buildSetter(name) { + return function(value) { + stack.push(["$", name, value]); + return ctx["$" + name] = value; } + } - function buildGetter(name) { - return function() { - return ctx["$" + name]; - } - } + for (var name in ctxProp) { + ctx["$" + name] = ctxProp[name]; + ctx.__defineGetter__(name, buildGetter(name)); - function buildSetter(name) { + // Special treatment for `fillStyle` and `strokeStyle`: The passed style + // might be a gradient. Need to check for that. + if (name == "fillStyle" || name == "strokeStyle") { + function buildSetterStyle(name) { return function(value) { + if (value instanceof GradientProxy) { + stack.push(["$" + name + "Gradient"]); + } else if (value instanceof PatternProxy) { + stack.push(["$" + name + "Pattern", [value.id]]); + } else { stack.push(["$", name, value]); return ctx["$" + name] = value; + } } + } + ctx.__defineSetter__(name, buildSetterStyle(name)); + } else { + ctx.__defineSetter__(name, buildSetter(name)); } - - for (var name in ctxProp) { - ctx["$" + name] = ctxProp[name]; - ctx.__defineGetter__(name, buildGetter(name)); - - // Special treatment for `fillStyle` and `strokeStyle`: The passed style - // might be a gradient. Need to check for that. - if (name == "fillStyle" || name == "strokeStyle") { - function buildSetterStyle(name) { - return function(value) { - if (value instanceof GradientProxy) { - stack.push(["$" + name + "Gradient"]); - } else if (value instanceof PatternProxy) { - stack.push(["$" + name + "Pattern", [value.id]]); - } else { - stack.push(["$", name, value]); - return ctx["$" + name] = value; - } - } - } - ctx.__defineSetter__(name, buildSetterStyle(name)); - } else { - ctx.__defineSetter__(name, buildSetter(name)); - } - } + } } +/** +* Sends the current stack of the CanvasProxy over to the main thread and +* resets the stack. +*/ CanvasProxy.prototype.flush = function() { - postMessage("canvas_proxy_stack"); - postMessage({ - id: this.id, - stack: this.$stack, - width: this.width, - height: this.height - }); - this.$stack.length = 0; + postMessage("canvas_proxy_stack"); + postMessage({ + id: this.id, + stack: this.$stack, + width: this.width, + height: this.height + }); + this.$stack.length = 0; } diff --git a/viewer_worker.html b/viewer_worker.html index 92806bc99..a9f08388f 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -1,295 +1,22 @@ <html> <head> <title>Simple pdf.js page viewer worker</title> + <script type="text/javascript" src="worker_client.js"></script> <script> -var timer = null -function tic() { - timer = Date.now(); -} - -function toc(msg) { - console.log(msg + ": " + (Date.now() - timer) + "ms"); -} - -var myWorker = new Worker('worker.js'); -var imagesList = {}; -var canvasList = {}; -var patternList = {}; -var gradient; - -var currentX = 0; -var currentXStack = []; -var special = { - "$setCurrentX": function(value) { - currentX = value; - }, - - "$addCurrentX": function(value) { - currentX += value; - }, - - "$saveCurrentX": function() { - currentXStack.push(currentX); - }, - - "$restoreCurrentX": function() { - currentX = currentXStack.pop(); - }, - - "$showText": function(y, text, uniText) { - this.translate(currentX, -1 * y); - this.fillText(uniText, 0, 0); - currentX += this.measureText(text).width; - }, - - "$putImageData": function(imageData, x, y) { - var imgData = this.getImageData(0, 0, imageData.width, imageData.height); - - // Store the .data property to avaid property lookups. - var imageRealData = imageData.data; - var imgRealData = imgData.data; - - // Copy over the imageData. - var len = imageRealData.length; - while (len--) - imgRealData[len] = imageRealData[len] - - this.putImageData(imgData, x, y); - }, - - "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { - var image = imagesList[id]; - if (!image) { - throw "Image not found"; - } - this.drawImage(image, x, y, image.width, image.height, - sx, sy, swidth, sheight); - }, - - "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) { - var canvas = canvasList[id]; - if (!canvas) { - throw "Canvas not found"; - } - if (sheight != null) { - this.drawImage(canvas, x, y, canvas.width, canvas.height, - sx, sy, swidth, sheight); - } else { - this.drawImage(canvas, x, y, canvas.width, canvas.height); - } - }, - - "$createLinearGradient": function(x0, y0, x1, y1) { - gradient = this.createLinearGradient(x0, y0, x1, y1); - }, - - "$createPatternFromCanvas": function(patternId, canvasId, kind) { - var canvas = canvasList[canvasId]; - if (!canvas) { - throw "Canvas not found"; - } - patternList[patternId] = this.createPattern(canvas, kind); - }, - - "$addColorStop": function(i, rgba) { - gradient.addColorStop(i, rgba); - }, - - "$fillStyleGradient": function() { - this.fillStyle = gradient; - }, - - "$fillStylePattern": function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw "Pattern not found"; - } - this.fillStyle = pattern; - }, - - "$strokeStyleGradient": function() { - this.strokeStyle = gradient; - }, - - "$strokeStylePattern": function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw "Pattern not found"; - } - this.strokeStyle = pattern; - } -} - -var gStack; -function renderProxyCanvas(canvas, stack) { - var ctx = canvas.getContext("2d"); - for (var i = 0; i < stack.length; i++) { - // for (var i = 0; i < 1000; i++) { - var opp = stack[i]; - if (opp[0] == "$") { - ctx[opp[1]] = opp[2]; - } else if (opp[0] in special) { - special[opp[0]].apply(ctx, opp[1]); - } else { - ctx[opp[0]].apply(ctx, opp[1]); - } - } -} - -const WAIT = 0; -const CANVAS_PROXY_STACK = 1; -const LOG = 2; -const FONT = 3; -const PDF_NUM_PAGE = 4; -const JPEG_STREAM = 5; - -var onMessageState = WAIT; -var fontStr = null; -var first = true; -var intervals = []; -myWorker.onmessage = function(event) { - var data = event.data; - // console.log("onMessageRaw", data); - switch (onMessageState) { - case WAIT: - if (typeof data != "string") { - throw "expecting to get an string"; - } - switch (data) { - case "pdf_num_page": - onMessageState = PDF_NUM_PAGE; - return; - case "log": - onMessageState = LOG; - return; - case "canvas_proxy_stack": - onMessageState = CANVAS_PROXY_STACK; - return; - case "font": - onMessageState = FONT; - return; - case "jpeg_stream": - onMessageState = JPEG_STREAM; - return; - default: - throw "unkown state: " + data - } - break; - - case JPEG_STREAM: - var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(data.str); - imagesList[data.id] = img; - console.log("got image", data.id) - break; - - case PDF_NUM_PAGE: - console.log(data); - maxPages = parseInt(data); - document.getElementById("numPages").innerHTML = "/" + data; - onMessageState = WAIT; - break; - - case FONT: - data = JSON.parse(data); - var base64 = window.btoa(data.str); - - // Add the @font-face rule to the document - var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); - - // Just adding the font-face to the DOM doesn't make it load. It - // seems it's loaded once Gecko notices it's used. Therefore, - // add a div on the page using the loaded font. - document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; - console.log("setup font", data.fontName); - - onMessageState = WAIT; - break; - - case LOG: - console.log.apply(console, JSON.parse(data)); - onMessageState = WAIT; - break; - - case CANVAS_PROXY_STACK: - var id = data.id; - var stack = data.stack; - gStack = stack; - - // Check if there is already a canvas with the given id. If not, - // create a new canvas. - if (!canvasList[id]) { - var newCanvas = document.createElement("canvas"); - newCanvas.width = data.width; - newCanvas.height = data.height; - canvasList[id] = newCanvas; - } - - // There might be fonts that need to get loaded. Shedule the - // rendering at the end of the event queue ensures this. - setTimeout(function() { - if (id == 0) tic(); - renderProxyCanvas(canvasList[id], stack); - if (id == 0) toc("canvas rendering") - }, 0); - onMessageState = WAIT; - break; - } -} -// -// myWorker.postMessage(array); - -var currentPage = 1; -var maxPages = 1; -function showPage(num) { - ctx.save(); - ctx.fillStyle = "rgb(255, 255, 255)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - console.log("worker: page=" + num) - document.getElementById('pageNumber').value = num; - currentPage = parseInt(num); - myWorker.postMessage(num); -} - -function open(url) { - document.title = url; - var req = new XMLHttpRequest(); - req.open("GET", url); - req.mozResponseType = req.responseType = "arraybuffer"; - req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; - req.onreadystatechange = function() { - if (req.readyState == 4 && req.status == req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || - req.responseArrayBuffer || req.response; - myWorker.postMessage(data); - showPage("2"); - } - }; - req.send(null); -} - -function nextPage() { - if (currentPage == maxPages) return; - currentPage++; - showPage(currentPage); -} - -function prevPage() { - if (currentPage == 1) return; - currentPage--; - showPage(currentPage); -} +var pdfDoc; window.onload = function() { window.canvas = document.getElementById("canvas"); window.ctx = canvas.getContext("2d"); - canvasList[0] = window.canvas; - open("compressed.tracemonkey-pldi-09.pdf"); + + pdfDoc = new WorkerPDFDoc(window.canvas); + pdfDoc.onChangePage = function(numPage) { + document.getElementById("pageNumber").value = numPage; + } + pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() { + document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages; + }) } </script> <link rel="stylesheet" href="viewer.css"></link> @@ -301,9 +28,9 @@ window.onload = function() { <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input> <!-- This only opens supported PDFs from the source path... -- Can we use JSONP to overcome the same-origin restrictions? --> - <button onclick="prevPage();">Previous</button> - <button onclick="nextPage();">Next</button> - <input type="text" id="pageNumber" onchange="showPage(this.value);" + <button onclick="pdfDoc.prevPage();">Previous</button> + <button onclick="pdfDoc.nextPage();">Next</button> + <input type="text" id="pageNumber" onchange="pdfDoc.showPage(this.value);" value="1" size="4"></input> <span id="numPages">--</span> <span id="info"></span> diff --git a/worker.js b/worker.js index e59e37155..09e2b8145 100644 --- a/worker.js +++ b/worker.js @@ -1,15 +1,26 @@ "use strict"; +var timer = null; +function tic() { + timer = Date.now(); +} + +function toc(msg) { + log(msg + ": " + (Date.now() - timer) + "ms"); + timer = null; +} + function log() { - var args = Array.prototype.slice.call(arguments); - postMessage("log"); - postMessage(JSON.stringify(args)) + var args = Array.prototype.slice.call(arguments); + postMessage("log"); + postMessage(JSON.stringify(args)) } var console = { - log: log + log: log } +// importScripts("canvas_proxy.js"); importScripts("pdf.js"); importScripts("fonts.js"); @@ -18,55 +29,50 @@ importScripts("glyphlist.js") // Use the JpegStreamProxy proxy. JpegStream = JpegStreamProxy; -var timer = null; -function tic() { - timer = Date.now(); -} - -function toc(msg) { - log(msg + ": " + (Date.now() - timer) + "ms"); - timer = null; -} - // Create the WebWorkerProxyCanvas. var canvas = new CanvasProxy(1224, 1584); -var pageInterval; +// Listen for messages from the main thread. var pdfDocument = null; onmessage = function(event) { - var data = event.data; - if (!pdfDocument) { - pdfDocument = new PDFDoc(new Stream(data)); - postMessage("pdf_num_page"); - postMessage(pdfDocument.numPages) - return; - } else { - tic(); + var data = event.data; + // If there is no pdfDocument yet, then the sent data is the PDFDocument. + if (!pdfDocument) { + pdfDocument = new PDFDoc(new Stream(data)); + postMessage("pdf_num_page"); + postMessage(pdfDocument.numPages) + return; + } + // User requested to render a certain page. + else { + tic(); - // Let's try to render the first page... - var page = pdfDocument.getPage(parseInt(data)); + // Let's try to render the first page... + var page = pdfDocument.getPage(parseInt(data)); - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); - page.compile(gfx, fonts); + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); + page.compile(gfx, fonts); - // Inspect fonts and translate the missing one. - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } + // Inspect fonts and translate the missing one. + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } - // This "builds" the font and sents it over to the main thread. - new Font(font.name, font.file, font.properties); - } - toc("compiled page"); - - page.display(gfx); - canvas.flush(); + // This "builds" the font and sents it over to the main thread. + new Font(font.name, font.file, font.properties); } + toc("compiled page"); + + tic() + page.display(gfx); + canvas.flush(); + toc("displayed page"); + } } diff --git a/worker_client.js b/worker_client.js new file mode 100644 index 000000000..316ef1fc0 --- /dev/null +++ b/worker_client.js @@ -0,0 +1,294 @@ +"use strict"; + +function WorkerPDFDoc(canvas) { + var timer = null + function tic() { + timer = Date.now(); + } + + function toc(msg) { + console.log(msg + ": " + (Date.now() - timer) + "ms"); + } + + this.ctx = canvas.getContext("2d"); + this.canvas = canvas; + this.worker = new Worker('worker.js'); + + this.numPage = 1; + this.numPages = null; + + var imagesList = {}; + var canvasList = { + 0: canvas + }; + var patternList = {}; + var gradient; + + var currentX = 0; + var currentXStack = []; + + var ctxSpecial = { + "$setCurrentX": function(value) { + currentX = value; + }, + + "$addCurrentX": function(value) { + currentX += value; + }, + + "$saveCurrentX": function() { + currentXStack.push(currentX); + }, + + "$restoreCurrentX": function() { + currentX = currentXStack.pop(); + }, + + "$showText": function(y, text, uniText) { + this.translate(currentX, -1 * y); + this.fillText(uniText, 0, 0); + currentX += this.measureText(text).width; + }, + + "$putImageData": function(imageData, x, y) { + var imgData = this.getImageData(0, 0, imageData.width, imageData.height); + + // Store the .data property to avaid property lookups. + var imageRealData = imageData.data; + var imgRealData = imgData.data; + + // Copy over the imageData. + var len = imageRealData.length; + while (len--) + imgRealData[len] = imageRealData[len] + + this.putImageData(imgData, x, y); + }, + + "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { + var image = imagesList[id]; + if (!image) { + throw "Image not found"; + } + this.drawImage(image, x, y, image.width, image.height, + sx, sy, swidth, sheight); + }, + + "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) { + var canvas = canvasList[id]; + if (!canvas) { + throw "Canvas not found"; + } + if (sheight != null) { + this.drawImage(canvas, x, y, canvas.width, canvas.height, + sx, sy, swidth, sheight); + } else { + this.drawImage(canvas, x, y, canvas.width, canvas.height); + } + }, + + "$createLinearGradient": function(x0, y0, x1, y1) { + gradient = this.createLinearGradient(x0, y0, x1, y1); + }, + + "$createPatternFromCanvas": function(patternId, canvasId, kind) { + var canvas = canvasList[canvasId]; + if (!canvas) { + throw "Canvas not found"; + } + patternList[patternId] = this.createPattern(canvas, kind); + }, + + "$addColorStop": function(i, rgba) { + gradient.addColorStop(i, rgba); + }, + + "$fillStyleGradient": function() { + this.fillStyle = gradient; + }, + + "$fillStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.fillStyle = pattern; + }, + + "$strokeStyleGradient": function() { + this.strokeStyle = gradient; + }, + + "$strokeStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.strokeStyle = pattern; + } + } + + function renderProxyCanvas(canvas, stack) { + var ctx = canvas.getContext("2d"); + for (var i = 0; i < stack.length; i++) { + var opp = stack[i]; + if (opp[0] == "$") { + ctx[opp[1]] = opp[2]; + } else if (opp[0] in ctxSpecial) { + ctxSpecial[opp[0]].apply(ctx, opp[1]); + } else { + ctx[opp[0]].apply(ctx, opp[1]); + } + } + } + + /** + * onMessage state machine. + */ + const WAIT = 0; + const CANVAS_PROXY_STACK = 1; + const LOG = 2; + const FONT = 3; + const PDF_NUM_PAGE = 4; + const JPEG_STREAM = 5; + + var onMessageState = WAIT; + this.worker.onmessage = function(event) { + var data = event.data; + // console.log("onMessageRaw", data); + switch (onMessageState) { + case WAIT: + if (typeof data != "string") { + throw "expecting to get an string"; + } + switch (data) { + case "pdf_num_page": + onMessageState = PDF_NUM_PAGE; + return; + + case "log": + onMessageState = LOG; + return; + + case "canvas_proxy_stack": + onMessageState = CANVAS_PROXY_STACK; + return; + + case "font": + onMessageState = FONT; + return; + + case "jpeg_stream": + onMessageState = JPEG_STREAM; + return; + + default: + throw "unkown state: " + data + } + break; + + case JPEG_STREAM: + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(data.str); + imagesList[data.id] = img; + console.log("got image", data.id) + break; + + case PDF_NUM_PAGE: + this.numPages = parseInt(data); + if (this.loadCallback) { + this.loadCallback(); + } + onMessageState = WAIT; + break; + + case FONT: + data = JSON.parse(data); + var base64 = window.btoa(data.str); + + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); + + // Just adding the font-face to the DOM doesn't make it load. It + // seems it's loaded once Gecko notices it's used. Therefore, + // add a div on the page using the loaded font. + document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; + + onMessageState = WAIT; + break; + + case LOG: + console.log.apply(console, JSON.parse(data)); + onMessageState = WAIT; + break; + + case CANVAS_PROXY_STACK: + var id = data.id; + var stack = data.stack; + + // Check if there is already a canvas with the given id. If not, + // create a new canvas. + if (!canvasList[id]) { + var newCanvas = document.createElement("canvas"); + newCanvas.width = data.width; + newCanvas.height = data.height; + canvasList[id] = newCanvas; + } + + // There might be fonts that need to get loaded. Shedule the + // rendering at the end of the event queue ensures this. + setTimeout(function() { + if (id == 0) tic(); + renderProxyCanvas(canvasList[id], stack); + if (id == 0) toc("canvas rendering") + }, 0); + onMessageState = WAIT; + break; + } + }.bind(this); +} + + WorkerPDFDoc.prototype.open = function(url, callback) { + var req = new XMLHttpRequest(); + req.open("GET", url); + req.mozResponseType = req.responseType = "arraybuffer"; + req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; + req.onreadystatechange = function() { + if (req.readyState == 4 && req.status == req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || + req.responseArrayBuffer || req.response; + + this.loadCallback = callback; + this.worker.postMessage(data); + this.showPage(this.numPage); + } + }.bind(this); + req.send(null); +} + +WorkerPDFDoc.prototype.showPage = function(numPage) { + var ctx = this.ctx; + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + this.numPage = parseInt(numPage); + this.worker.postMessage(numPage); + if (this.onChangePage) { + this.onChangePage(numPage); + } +} + +WorkerPDFDoc.prototype.nextPage = function() { + if (this.numPage == this.numPages) return; + this.showPage(++this.numPage); +} + +WorkerPDFDoc.prototype.prevPage = function() { + if (this.numPage == 1) return; + this.showPage(--this.numPage); +} From a3d815074dd9d186c26fb6ecf9eed9cd0fb6a60d Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 13:09:36 +0200 Subject: [PATCH 27/45] First pass on review: worker.js -> pdf_worker.js, Font.bind cleanup + other stuff --- canvas_proxy.js | 73 ++++++++++++++++-------------- fonts.js | 92 +++++++------------------------------- pdf.js | 8 ++-- worker.js => pdf_worker.js | 3 ++ viewer_worker.html | 1 + worker_client.js | 23 ++++++---- 6 files changed, 77 insertions(+), 123 deletions(-) rename worker.js => pdf_worker.js (92%) diff --git a/canvas_proxy.js b/canvas_proxy.js index 83b57682f..0b7681bfe 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,3 +1,7 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +"use strict"; var JpegStreamProxyCounter = 0; // WebWorker Proxy for JpegStream. @@ -29,16 +33,16 @@ var JpegStreamProxy = (function() { // Really simple GradientProxy. There is currently only one active gradient at // the time, meaning you can't create a gradient, create a second one and then // use the first one again. As this isn't used in pdf.js right now, it's okay. -function GradientProxy(stack, x0, y0, x1, y1) { - stack.push(["$createLinearGradient", [x0, y0, x1, y1]]); +function GradientProxy(cmdQueue, x0, y0, x1, y1) { + cmdQueue.push(["$createLinearGradient", [x0, y0, x1, y1]]); this.addColorStop = function(i, rgba) { - stack.push(["$addColorStop", [i, rgba]]); + cmdQueue.push(["$addColorStop", [i, rgba]]); } } // Really simple PatternProxy. var patternProxyCounter = 0; -function PatternProxy(stack, object, kind) { +function PatternProxy(cmdQueue, object, kind) { this.id = patternProxyCounter++; if (!(object instanceof CanvasProxy) ) { @@ -49,7 +53,7 @@ function PatternProxy(stack, object, kind) { // TODO: Make some kind of dependency management, such that the object // gets flushed only if needed. object.flush(); - stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); + cmdQueue.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); } var canvasProxyCounter = 0; @@ -57,7 +61,7 @@ function CanvasProxy(width, height) { this.id = canvasProxyCounter++; // The `stack` holds the rendering calls and gets flushed to the main thead. - var stack = this.$stack = []; + var cmdQueue = this.cmdQueue = []; // Dummy context that gets exposed. var ctx = {}; @@ -119,7 +123,7 @@ function CanvasProxy(width, height) { function buildFuncCall(name) { return function() { // console.log("funcCall", name) - stack.push([name, Array.prototype.slice.call(arguments)]); + cmdQueue.push([name, Array.prototype.slice.call(arguments)]); } } var name; @@ -131,11 +135,11 @@ function CanvasProxy(width, height) { // Some function calls that need more work. ctx.createPattern = function(object, kind) { - return new PatternProxy(stack, object, kind); + return new PatternProxy(cmdQueue, object, kind); } ctx.createLinearGradient = function(x0, y0, x1, y1) { - return new GradientProxy(stack, x0, y0, x1, y1); + return new GradientProxy(cmdQueue, x0, y0, x1, y1); } ctx.getImageData = function(x, y, w, h) { @@ -147,16 +151,16 @@ function CanvasProxy(width, height) { } ctx.putImageData = function(data, x, y, width, height) { - stack.push(["$putImageData", [data, x, y, width, height]]); + cmdQueue.push(["$putImageData", [data, x, y, width, height]]); } ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { if (image instanceof CanvasProxy) { // Send the image/CanvasProxy to the main thread. image.flush(); - stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); + cmdQueue.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); } else if(image instanceof JpegStreamProxy) { - stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) + cmdQueue.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) } else { throw "unkown type to drawImage"; } @@ -192,11 +196,26 @@ function CanvasProxy(width, height) { function buildSetter(name) { return function(value) { - stack.push(["$", name, value]); + cmdQueue.push(["$", name, value]); return ctx["$" + name] = value; } } + // Setting the value to `stroke|fillStyle` needs special handling, as it + // might gets an gradient/pattern. + function buildSetterStyle(name) { + return function(value) { + if (value instanceof GradientProxy) { + cmdQueue.push(["$" + name + "Gradient"]); + } else if (value instanceof PatternProxy) { + cmdQueue.push(["$" + name + "Pattern", [value.id]]); + } else { + cmdQueue.push(["$", name, value]); + return ctx["$" + name] = value; + } + } + } + for (var name in ctxProp) { ctx["$" + name] = ctxProp[name]; ctx.__defineGetter__(name, buildGetter(name)); @@ -204,18 +223,6 @@ function CanvasProxy(width, height) { // Special treatment for `fillStyle` and `strokeStyle`: The passed style // might be a gradient. Need to check for that. if (name == "fillStyle" || name == "strokeStyle") { - function buildSetterStyle(name) { - return function(value) { - if (value instanceof GradientProxy) { - stack.push(["$" + name + "Gradient"]); - } else if (value instanceof PatternProxy) { - stack.push(["$" + name + "Pattern", [value.id]]); - } else { - stack.push(["$", name, value]); - return ctx["$" + name] = value; - } - } - } ctx.__defineSetter__(name, buildSetterStyle(name)); } else { ctx.__defineSetter__(name, buildSetter(name)); @@ -224,16 +231,16 @@ function CanvasProxy(width, height) { } /** -* Sends the current stack of the CanvasProxy over to the main thread and -* resets the stack. +* Sends the current cmdQueue of the CanvasProxy over to the main thread and +* resets the cmdQueue. */ CanvasProxy.prototype.flush = function() { - postMessage("canvas_proxy_stack"); + postMessage("canvas_proxy_cmd_queue"); postMessage({ - id: this.id, - stack: this.$stack, - width: this.width, - height: this.height + id: this.id, + cmdQueue: this.cmdQueue, + width: this.width, + height: this.height }); - this.$stack.length = 0; + this.cmdQueue.length = 0; } diff --git a/fonts.js b/fonts.js index 9c9201b72..a3604c6b9 100644 --- a/fonts.js +++ b/fonts.js @@ -759,88 +759,15 @@ var Font = (function () { var data = this.font; var fontName = this.name; - var isWorker = (typeof window == "undefined"); - /** Hack begin */ - if (!isWorker) { - - // Actually there is not event when a font has finished downloading so - // the following code are a dirty hack to 'guess' when a font is ready - var canvas = document.createElement("canvas"); - var style = "border: 1px solid black; position:absolute; top: " + - (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); - - // Get the font size canvas think it will be for 'spaces' - var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var testString = " "; - - // When debugging use the characters provided by the charsets to visually - // see what's happening instead of 'spaces' - var debug = false; - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); - - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; - - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); - - for (var i = 0; i < charset.length; i++) { - var unicode = GlyphsUnicode[charset[i]]; - if (!unicode) - continue; - testString += String.fromCharCode(unicode); - } - - ctx.fillText(testString, 20, 20); - } - - // Periodicaly check for the width of the testString, it will be - // different once the real font has loaded - var textWidth = ctx.measureText(testString).width; - - var interval = window.setInterval(function canvasInterval(self) { - this.start = this.start || Date.now(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - - // For some reasons the font has not loaded, so mark it loaded for the - // page to proceed but cry - if ((Date.now() - this.start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); - this.start = 0; - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - this.start = 0; - } - - if (debug) - ctx.fillText(testString, 20, 50); - }, 30, this); - } - - /** Hack end */ - // // Get the base64 encoding of the binary font data var str = ""; var length = data.length; for (var i = 0; i < length; ++i) str += String.fromCharCode(data[i]); - if (isWorker) { + // Insert the font-face css on the page. In a web worker, this needs to + // be forwareded on the main thread. + if (typeof window == "undefined") { postMessage("font"); postMessage(JSON.stringify({ str: str, @@ -855,6 +782,19 @@ var Font = (function () { var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); + + var div = document.createElement("div"); + div.innerHTML += "<div style='font-family:" + + fontName + + ";'>j</div>"; + document.body.appendChild(div); + + Fonts[fontName].loading = true; + window.setTimeout(function() { + Fonts[fontName].loading = false; + // Timeout of just `0`, `10` doesn't work here, but for me all values + // above work. Setting value to 50ms. + }, 50); } } }; diff --git a/pdf.js b/pdf.js index 1223a2bb6..847067946 100644 --- a/pdf.js +++ b/pdf.js @@ -2645,9 +2645,7 @@ var CanvasGraphics = (function() { } var fn = Function("objpool", src); - var ret = function (gfx) { fn.call(gfx, objpool); }; - ret.src = src; - return ret; + return function (gfx) { fn.call(gfx, objpool); }; }, endDrawing: function() { @@ -3015,8 +3013,8 @@ var CanvasGraphics = (function() { var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); var tmpCanvas = new this.ScratchCanvas( - Math.ceil(botRight[0] - topLeft[0]), // WIDTH - Math.ceil(botRight[1] - topLeft[1]) // HEIGHT + Math.ceil(botRight[0] - topLeft[0]), // width + Math.ceil(botRight[1] - topLeft[1]) // height ); // set the new canvas element context as the graphics context diff --git a/worker.js b/pdf_worker.js similarity index 92% rename from worker.js rename to pdf_worker.js index 09e2b8145..91245aedb 100644 --- a/worker.js +++ b/pdf_worker.js @@ -1,3 +1,6 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + "use strict"; var timer = null; diff --git a/viewer_worker.html b/viewer_worker.html index a9f08388f..a5ffc6a6e 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -14,6 +14,7 @@ window.onload = function() { pdfDoc.onChangePage = function(numPage) { document.getElementById("pageNumber").value = numPage; } + // pdfDoc.open("canvas.pdf", function() { pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() { document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages; }) diff --git a/worker_client.js b/worker_client.js index 316ef1fc0..f69f4f682 100644 --- a/worker_client.js +++ b/worker_client.js @@ -1,3 +1,6 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + "use strict"; function WorkerPDFDoc(canvas) { @@ -128,10 +131,11 @@ function WorkerPDFDoc(canvas) { } } - function renderProxyCanvas(canvas, stack) { + function renderProxyCanvas(canvas, cmdQueue) { var ctx = canvas.getContext("2d"); - for (var i = 0; i < stack.length; i++) { - var opp = stack[i]; + var cmdQueueLength = cmdQueue.length; + for (var i = 0; i < cmdQueueLength; i++) { + var opp = cmdQueue[i]; if (opp[0] == "$") { ctx[opp[1]] = opp[2]; } else if (opp[0] in ctxSpecial) { @@ -146,7 +150,7 @@ function WorkerPDFDoc(canvas) { * onMessage state machine. */ const WAIT = 0; - const CANVAS_PROXY_STACK = 1; + const CANVAS_PROXY_CMD_QUEUE = 1; const LOG = 2; const FONT = 3; const PDF_NUM_PAGE = 4; @@ -170,8 +174,8 @@ function WorkerPDFDoc(canvas) { onMessageState = LOG; return; - case "canvas_proxy_stack": - onMessageState = CANVAS_PROXY_STACK; + case "canvas_proxy_cmd_queue": + onMessageState = CANVAS_PROXY_CMD_QUEUE; return; case "font": @@ -215,6 +219,7 @@ function WorkerPDFDoc(canvas) { // Just adding the font-face to the DOM doesn't make it load. It // seems it's loaded once Gecko notices it's used. Therefore, // add a div on the page using the loaded font. + var div = document.createElement("div"); document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; onMessageState = WAIT; @@ -225,9 +230,9 @@ function WorkerPDFDoc(canvas) { onMessageState = WAIT; break; - case CANVAS_PROXY_STACK: + case CANVAS_PROXY_CMD_QUEUE: var id = data.id; - var stack = data.stack; + var cmdQueue = data.cmdQueue; // Check if there is already a canvas with the given id. If not, // create a new canvas. @@ -242,7 +247,7 @@ function WorkerPDFDoc(canvas) { // rendering at the end of the event queue ensures this. setTimeout(function() { if (id == 0) tic(); - renderProxyCanvas(canvasList[id], stack); + renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) toc("canvas rendering") }, 0); onMessageState = WAIT; From da7f555fd7cccfb8cef331438fec7e905059373d Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 13:25:59 +0200 Subject: [PATCH 28/45] Change postMessage to send only one object that holds the action and data. --- canvas_proxy.js | 17 +++--- fonts.js | 10 ++-- pdf_worker.js | 12 +++-- worker_client.js | 136 +++++++++++++++++------------------------------ 4 files changed, 72 insertions(+), 103 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 0b7681bfe..e2795bd00 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -11,10 +11,9 @@ var JpegStreamProxy = (function() { this.dict = dict; // Tell the main thread to create an image. - postMessage("jpeg_stream"); postMessage({ - id: this.id, - str: bytesToString(bytes) + action: jpeg_stream, + data: bytesToString(bytes) }); } @@ -235,12 +234,14 @@ function CanvasProxy(width, height) { * resets the cmdQueue. */ CanvasProxy.prototype.flush = function() { - postMessage("canvas_proxy_cmd_queue"); postMessage({ - id: this.id, - cmdQueue: this.cmdQueue, - width: this.width, - height: this.height + action: "canvas_proxy_cmd_queue", + data: { + id: this.id, + cmdQueue: this.cmdQueue, + width: this.width, + height: this.height + } }); this.cmdQueue.length = 0; } diff --git a/fonts.js b/fonts.js index a3604c6b9..7f4958caf 100644 --- a/fonts.js +++ b/fonts.js @@ -768,12 +768,14 @@ var Font = (function () { // Insert the font-face css on the page. In a web worker, this needs to // be forwareded on the main thread. if (typeof window == "undefined") { - postMessage("font"); - postMessage(JSON.stringify({ - str: str, + postMessage({ + action: "font", + data: { + raw: str, fontName: fontName, mimetype: this.mimetype - })); + } + }); } else { var base64 = window.btoa(str); diff --git a/pdf_worker.js b/pdf_worker.js index 91245aedb..13a1f3f28 100644 --- a/pdf_worker.js +++ b/pdf_worker.js @@ -15,8 +15,10 @@ function toc(msg) { function log() { var args = Array.prototype.slice.call(arguments); - postMessage("log"); - postMessage(JSON.stringify(args)) + postMessage({ + action: "log", + args: args + }); } var console = { @@ -42,8 +44,10 @@ onmessage = function(event) { // If there is no pdfDocument yet, then the sent data is the PDFDocument. if (!pdfDocument) { pdfDocument = new PDFDoc(new Stream(data)); - postMessage("pdf_num_page"); - postMessage(pdfDocument.numPages) + postMessage({ + action: "pdf_num_pages", + data: pdfDocument.numPages + }); return; } // User requested to render a certain page. diff --git a/worker_client.js b/worker_client.js index f69f4f682..4af0d9764 100644 --- a/worker_client.js +++ b/worker_client.js @@ -15,7 +15,7 @@ function WorkerPDFDoc(canvas) { this.ctx = canvas.getContext("2d"); this.canvas = canvas; - this.worker = new Worker('worker.js'); + this.worker = new Worker('pdf_worker.js'); this.numPage = 1; this.numPages = null; @@ -147,90 +147,44 @@ function WorkerPDFDoc(canvas) { } /** - * onMessage state machine. + * Functions to handle data sent by the WebWorker. */ - const WAIT = 0; - const CANVAS_PROXY_CMD_QUEUE = 1; - const LOG = 2; - const FONT = 3; - const PDF_NUM_PAGE = 4; - const JPEG_STREAM = 5; - - var onMessageState = WAIT; - this.worker.onmessage = function(event) { - var data = event.data; - // console.log("onMessageRaw", data); - switch (onMessageState) { - case WAIT: - if (typeof data != "string") { - throw "expecting to get an string"; - } - switch (data) { - case "pdf_num_page": - onMessageState = PDF_NUM_PAGE; - return; - - case "log": - onMessageState = LOG; - return; - - case "canvas_proxy_cmd_queue": - onMessageState = CANVAS_PROXY_CMD_QUEUE; - return; - - case "font": - onMessageState = FONT; - return; - - case "jpeg_stream": - onMessageState = JPEG_STREAM; - return; + var actionHandler = { + "log": function(data) { + console.log.apply(console, data); + }, + + "pdf_num_pages": function(data) { + this.numPages = parseInt(data); + if (this.loadCallback) { + this.loadCallback(); + } + }, + + "font": function(data) { + var base64 = window.btoa(data.raw); + + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); + + // Just adding the font-face to the DOM doesn't make it load. It + // seems it's loaded once Gecko notices it's used. Therefore, + // add a div on the page using the loaded font. + var div = document.createElement("div"); + document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; + }, - default: - throw "unkown state: " + data - } - break; - - case JPEG_STREAM: - var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(data.str); - imagesList[data.id] = img; - console.log("got image", data.id) - break; - - case PDF_NUM_PAGE: - this.numPages = parseInt(data); - if (this.loadCallback) { - this.loadCallback(); - } - onMessageState = WAIT; - break; - - case FONT: - data = JSON.parse(data); - var base64 = window.btoa(data.str); - - // Add the @font-face rule to the document - var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); - - // Just adding the font-face to the DOM doesn't make it load. It - // seems it's loaded once Gecko notices it's used. Therefore, - // add a div on the page using the loaded font. - var div = document.createElement("div"); - document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; - - onMessageState = WAIT; - break; - - case LOG: - console.log.apply(console, JSON.parse(data)); - onMessageState = WAIT; - break; - - case CANVAS_PROXY_CMD_QUEUE: + "jpeg_stream": function(data) { + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(data); + imagesList[data.id] = img; + console.log("got image", data.id) + }, + + "canvas_proxy_cmd_queue": function(data) { var id = data.id; var cmdQueue = data.cmdQueue; @@ -250,13 +204,21 @@ function WorkerPDFDoc(canvas) { renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) toc("canvas rendering") }, 0); - onMessageState = WAIT; - break; } - }.bind(this); + } + + // List to the WebWorker for data and call actionHandler on it. + this.worker.onmessage = function(event) { + var data = event.data; + if (data.action in actionHandler) { + actionHandler[data.action].call(this, data.data); + } else { + throw "Unkown action from worker: " + data.action; + } + } } - WorkerPDFDoc.prototype.open = function(url, callback) { +WorkerPDFDoc.prototype.open = function(url, callback) { var req = new XMLHttpRequest(); req.open("GET", url); req.mozResponseType = req.responseType = "arraybuffer"; From f32dfa9414eef8bdaa5b5e65f0d1b1fcb02de3ce Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 14:36:45 +0200 Subject: [PATCH 29/45] Fix sending image data to main thread --- canvas_proxy.js | 7 +++++-- viewer.js | 2 +- worker_client.js | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index e2795bd00..d6f5a0a25 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -12,8 +12,11 @@ var JpegStreamProxy = (function() { // Tell the main thread to create an image. postMessage({ - action: jpeg_stream, - data: bytesToString(bytes) + action: "jpeg_stream", + data: { + id: this.id, + raw: bytesToString(bytes) + } }); } diff --git a/viewer.js b/viewer.js index 41aaf354c..d0aeb0b2d 100644 --- a/viewer.js +++ b/viewer.js @@ -10,7 +10,7 @@ function load(userInput) { pageNum = parseInt(queryParams().page) || 1; var fileName = userInput; if (!userInput) { - fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; + fileName = "canvas.pdf"; } open(fileName); } diff --git a/worker_client.js b/worker_client.js index 4af0d9764..385103c30 100644 --- a/worker_client.js +++ b/worker_client.js @@ -71,7 +71,7 @@ function WorkerPDFDoc(canvas) { "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { var image = imagesList[id]; if (!image) { - throw "Image not found"; + throw "Image not found: " + id; } this.drawImage(image, x, y, image.width, image.height, sx, sy, swidth, sheight); @@ -179,9 +179,8 @@ function WorkerPDFDoc(canvas) { "jpeg_stream": function(data) { var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(data); + img.src = "data:image/jpeg;base64," + window.btoa(data.raw); imagesList[data.id] = img; - console.log("got image", data.id) }, "canvas_proxy_cmd_queue": function(data) { From f256716022c86a216ec6bc15e6c12644d43d9e80 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 15:24:55 +0200 Subject: [PATCH 30/45] Fix WebWorker logging and add separate timing for `fonts`. --- pdf_worker.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pdf_worker.js b/pdf_worker.js index 13a1f3f28..86dfec2dd 100644 --- a/pdf_worker.js +++ b/pdf_worker.js @@ -17,7 +17,7 @@ function log() { var args = Array.prototype.slice.call(arguments); postMessage({ action: "log", - args: args + data: args }); } @@ -62,7 +62,9 @@ onmessage = function(event) { var fonts = []; var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); page.compile(gfx, fonts); + toc("compiled page"); + tic() // Inspect fonts and translate the missing one. var count = fonts.length; for (var i = 0; i < count; i++) { @@ -75,7 +77,7 @@ onmessage = function(event) { // This "builds" the font and sents it over to the main thread. new Font(font.name, font.file, font.properties); } - toc("compiled page"); + toc("fonts"); tic() page.display(gfx); From 4b27045d2e45e9aa370a0ed2299cd83fef7f78aa Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 19:43:01 +0200 Subject: [PATCH 31/45] Ensure divs used to make fonts load are not visible --- fonts.js | 6 +++--- viewer.js | 2 +- viewer_worker.html | 2 -- worker_client.js | 5 ++++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index 7f4958caf..ba40ef8e1 100644 --- a/fonts.js +++ b/fonts.js @@ -786,9 +786,9 @@ var Font = (function () { styleSheet.insertRule(rule, styleSheet.length); var div = document.createElement("div"); - div.innerHTML += "<div style='font-family:" + - fontName + - ";'>j</div>"; + var style = 'font-family:"' + fontName + + '";position: absolute;top:-99999;left:-99999;z-index:-99999'; + div.setAttribute("style", style); document.body.appendChild(div); Fonts[fontName].loading = true; diff --git a/viewer.js b/viewer.js index d0aeb0b2d..41aaf354c 100644 --- a/viewer.js +++ b/viewer.js @@ -10,7 +10,7 @@ function load(userInput) { pageNum = parseInt(queryParams().page) || 1; var fileName = userInput; if (!userInput) { - fileName = "canvas.pdf"; + fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; } open(fileName); } diff --git a/viewer_worker.html b/viewer_worker.html index a5ffc6a6e..d13935f13 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -22,9 +22,7 @@ window.onload = function() { </script> <link rel="stylesheet" href="viewer.css"></link> </head> - <body> - <div id="fonts" style="position: absolute;display:block;z-index:-1;"></div> <div id="controls"> <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input> <!-- This only opens supported PDFs from the source path... diff --git a/worker_client.js b/worker_client.js index 385103c30..62a7c1377 100644 --- a/worker_client.js +++ b/worker_client.js @@ -174,7 +174,10 @@ function WorkerPDFDoc(canvas) { // seems it's loaded once Gecko notices it's used. Therefore, // add a div on the page using the loaded font. var div = document.createElement("div"); - document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>"; + var style = 'font-family:"' + data.fontName + + '";position: absolute;top:-99999;left:-99999;z-index:-99999'; + div.setAttribute("style", style); + document.body.appendChild(div); }, "jpeg_stream": function(data) { From d6239571e945c9008ea1156ad755651773f4118a Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 22:24:41 +0200 Subject: [PATCH 32/45] Clear the main canvas right before the next rendering begins. Keeps the canvas from beeing blank for a few ms --- worker_client.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/worker_client.js b/worker_client.js index 62a7c1377..c1e124693 100644 --- a/worker_client.js +++ b/worker_client.js @@ -202,10 +202,17 @@ function WorkerPDFDoc(canvas) { // There might be fonts that need to get loaded. Shedule the // rendering at the end of the event queue ensures this. setTimeout(function() { - if (id == 0) tic(); + if (id == 0) { + tic(); + var ctx = this.ctx; + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + } renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) toc("canvas rendering") - }, 0); + }, 0, this); } } @@ -239,12 +246,6 @@ WorkerPDFDoc.prototype.open = function(url, callback) { } WorkerPDFDoc.prototype.showPage = function(numPage) { - var ctx = this.ctx; - ctx.save(); - ctx.fillStyle = "rgb(255, 255, 255)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - this.numPage = parseInt(numPage); this.worker.postMessage(numPage); if (this.onChangePage) { From 3bef1534b4fe8d659c35b890fcf07bcb053d049f Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 22:55:26 +0200 Subject: [PATCH 33/45] Reverts parts of 60f4d16360: Use old font-is-loaded mechanism + some code refactoring to add bindDOM and bindWorker. --- fonts.js | 149 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 43 deletions(-) diff --git a/fonts.js b/fonts.js index ba40ef8e1..f00f5c75f 100644 --- a/fonts.js +++ b/fonts.js @@ -135,15 +135,28 @@ var Font = (function () { break; } + var data = this.font; Fonts[name] = { - data: this.font, + data: data, properties: properties, loading: true, cache: Object.create(null) } - // Attach the font to the document - this.bind(); + // Convert data to a string. + var dataStr = ""; + var length = data.length; + for (var i = 0; i < length; ++i) + dataStr += String.fromCharCode(data[i]); + + // Attach the font to the document. If this script is runnig in a worker, + // call `bindWorker`, which sends stuff over to the main thread. + if (typeof window != "undefined") { + this.bindDOM(dataStr); + } else { + this.bindWorker(dataStr); + } + }; function stringToArray(str) { @@ -755,49 +768,99 @@ var Font = (function () { return fontData; }, - bind: function font_bind() { - var data = this.font; + bindWorker: function font_bind_worker(dataStr) { + postMessage({ + action: "font", + data: { + raw: dataStr, + fontName: this.name, + mimetype: this.mimetype + } + }); + }, + + bindDOM: function font_bind_dom(dataStr) { var fontName = this.name; - // Get the base64 encoding of the binary font data - var str = ""; - var length = data.length; - for (var i = 0; i < length; ++i) - str += String.fromCharCode(data[i]); - - // Insert the font-face css on the page. In a web worker, this needs to - // be forwareded on the main thread. - if (typeof window == "undefined") { - postMessage({ - action: "font", - data: { - raw: str, - fontName: fontName, - mimetype: this.mimetype - } - }); - } else { - var base64 = window.btoa(str); - - // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); - - var div = document.createElement("div"); - var style = 'font-family:"' + fontName + - '";position: absolute;top:-99999;left:-99999;z-index:-99999'; - div.setAttribute("style", style); - document.body.appendChild(div); - - Fonts[fontName].loading = true; - window.setTimeout(function() { - Fonts[fontName].loading = false; - // Timeout of just `0`, `10` doesn't work here, but for me all values - // above work. Setting value to 50ms. - }, 50); + /** Hack begin */ + // Actually there is not event when a font has finished downloading so + // the following code are a dirty hack to 'guess' when a font is ready + var canvas = document.createElement("canvas"); + var style = "border: 1px solid black; position:absolute; top: " + + (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; + canvas.setAttribute("style", style); + canvas.setAttribute("width", 340); + canvas.setAttribute("heigth", 100); + document.body.appendChild(canvas); + + // Get the font size canvas think it will be for 'spaces' + var ctx = canvas.getContext("2d"); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var testString = " "; + + // When debugging use the characters provided by the charsets to visually + // see what's happening instead of 'spaces' + var debug = false; + if (debug) { + var name = document.createElement("font"); + name.setAttribute("style", "position: absolute; left: 20px; top: " + + (100 * fontCount + 60) + "px"); + name.innerHTML = fontName; + document.body.appendChild(name); + + // Retrieve font charset + var charset = Fonts[fontName].properties.charset || []; + + // if the charset is too small make it repeat a few times + var count = 30; + while (count-- && charset.length <= 30) + charset = charset.concat(charset.slice()); + + for (var i = 0; i < charset.length; i++) { + var unicode = GlyphsUnicode[charset[i]]; + if (!unicode) + continue; + testString += String.fromCharCode(unicode); + } + + ctx.fillText(testString, 20, 20); } + + // Periodicaly check for the width of the testString, it will be + // different once the real font has loaded + var textWidth = ctx.measureText(testString).width; + + var interval = window.setInterval(function canvasInterval(self) { + this.start = this.start || Date.now(); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + + // For some reasons the font has not loaded, so mark it loaded for the + // page to proceed but cry + if ((Date.now() - this.start) >= kMaxWaitForFontFace) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + warn("Is " + fontName + " for charset: " + charset + " loaded?"); + this.start = 0; + } else if (textWidth != ctx.measureText(testString).width) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + this.start = 0; + } + + if (debug) + ctx.fillText(testString, 20, 50); + }, 30, this); + + /** Hack end */ + + // Convert the data string and add it to the page. + var base64 = window.btoa(dataStr); + + // Add the @font-face rule to the document + var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); } }; From d1b75dd6331461b0def4e5fdecb95b111561870b Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 23:06:33 +0200 Subject: [PATCH 34/45] Replace tic & toc by console.time/timeEnd --- pdf_worker.js | 51 ++++++++++++++++++++++++------------------------ worker_client.js | 26 +++++++++++++++--------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/pdf_worker.js b/pdf_worker.js index 86dfec2dd..fa29428c7 100644 --- a/pdf_worker.js +++ b/pdf_worker.js @@ -3,26 +3,27 @@ "use strict"; -var timer = null; -function tic() { - timer = Date.now(); -} - -function toc(msg) { - log(msg + ": " + (Date.now() - timer) + "ms"); - timer = null; -} - -function log() { - var args = Array.prototype.slice.call(arguments); - postMessage({ - action: "log", - data: args - }); -} - +var consoleTimer = {}; var console = { - log: log + log: function log() { + var args = Array.prototype.slice.call(arguments); + postMessage({ + action: "log", + data: args + }); + }, + + time: function(name) { + consoleTimer[name] = Date.now(); + }, + + timeEnd: function(name) { + var time = consoleTimer[name]; + if (time == null) { + throw "Unkown timer name " + name; + } + this.log("Timer:", name, Date.now() - time); + } } // @@ -52,7 +53,7 @@ onmessage = function(event) { } // User requested to render a certain page. else { - tic(); + console.time("compile"); // Let's try to render the first page... var page = pdfDocument.getPage(parseInt(data)); @@ -62,9 +63,9 @@ onmessage = function(event) { var fonts = []; var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); page.compile(gfx, fonts); - toc("compiled page"); + console.timeEnd("compile"); - tic() + console.time("fonts"); // Inspect fonts and translate the missing one. var count = fonts.length; for (var i = 0; i < count; i++) { @@ -77,11 +78,11 @@ onmessage = function(event) { // This "builds" the font and sents it over to the main thread. new Font(font.name, font.file, font.properties); } - toc("fonts"); + console.timeEnd("fonts"); - tic() + console.time("display"); page.display(gfx); canvas.flush(); - toc("displayed page"); + console.timeEnd("display"); } } diff --git a/worker_client.js b/worker_client.js index c1e124693..b39374af1 100644 --- a/worker_client.js +++ b/worker_client.js @@ -3,15 +3,23 @@ "use strict"; +if (typeof console.time == "undefined") { + var consoleTimer = {}; + console.time = function(name) { + consoleTimer[name] = Date.now(); + }; + + console.timeEnd = function(name) { + var time = consoleTimer[name]; + if (time == null) { + throw "Unkown timer name " + name; + } + this.log("Timer:", name, Date.now() - time); + }; +} + function WorkerPDFDoc(canvas) { var timer = null - function tic() { - timer = Date.now(); - } - - function toc(msg) { - console.log(msg + ": " + (Date.now() - timer) + "ms"); - } this.ctx = canvas.getContext("2d"); this.canvas = canvas; @@ -203,7 +211,7 @@ function WorkerPDFDoc(canvas) { // rendering at the end of the event queue ensures this. setTimeout(function() { if (id == 0) { - tic(); + console.time("canvas rendering"); var ctx = this.ctx; ctx.save(); ctx.fillStyle = "rgb(255, 255, 255)"; @@ -211,7 +219,7 @@ function WorkerPDFDoc(canvas) { ctx.restore(); } renderProxyCanvas(canvasList[id], cmdQueue); - if (id == 0) toc("canvas rendering") + if (id == 0) console.timeEnd("canvas rendering") }, 0, this); } } From 8cd986f15aa4a676554bb78b8f5fc2e886af5046 Mon Sep 17 00:00:00 2001 From: Julian Viereck <julian.viereck@gmail.com> Date: Thu, 23 Jun 2011 23:55:14 +0200 Subject: [PATCH 35/45] Small rebase tidyup + cleanup --- pdf.js | 24 +++++++++++------------- viewer_worker.html | 2 +- worker_client.js | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/pdf.js b/pdf.js index 847067946..da5926c09 100644 --- a/pdf.js +++ b/pdf.js @@ -2287,6 +2287,14 @@ var CanvasGraphics = (function() { this.ScratchCanvas = imageCanvas || ScratchCanvas; } + var LINE_CAP_STYLES = [ "butt", "round", "square" ]; + var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + // Used for tiling patterns + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + constructor.prototype = { map: { // Graphics state @@ -2381,18 +2389,8 @@ var CanvasGraphics = (function() { // Compatibility BX: "beginCompat", EX: "endCompat", - }; - } - - var LINE_CAP_STYLES = [ "butt", "round", "square" ]; - var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; - var NORMAL_CLIP = {}; - var EO_CLIP = {}; - - // Used for tiling patterns - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - constructor.prototype = { + }, + translateFont: function(fontDict, xref, resources) { var fd = fontDict.get("FontDescriptor"); if (!fd) @@ -2855,7 +2853,7 @@ var CanvasGraphics = (function() { this.ctx.scale(1, -1); if (this.ctx.$showText) { - this.ctx.$showText(this.current.y, text, Fonts.charsToUnicode(text)); + this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); } else { text = Fonts.charsToUnicode(text); this.ctx.translate(this.current.x, -1 * this.current.y); diff --git a/viewer_worker.html b/viewer_worker.html index d13935f13..51f2b9d8a 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -1,6 +1,6 @@ <html> <head> - <title>Simple pdf.js page viewer worker</title> + <title>Simple pdf.js page worker viewer</title> <script type="text/javascript" src="worker_client.js"></script> <script> diff --git a/worker_client.js b/worker_client.js index b39374af1..359a5f7c1 100644 --- a/worker_client.js +++ b/worker_client.js @@ -55,9 +55,9 @@ function WorkerPDFDoc(canvas) { currentX = currentXStack.pop(); }, - "$showText": function(y, text, uniText) { + "$showText": function(y, text) { this.translate(currentX, -1 * y); - this.fillText(uniText, 0, 0); + this.fillText(text, 0, 0); currentX += this.measureText(text).width; }, From f2126374334ec82e8d2473fc13b56d70ba6cd3f8 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 15:09:17 -0700 Subject: [PATCH 36/45] Remove invalid trailing commas. --- test/resources/browser_manifests/browser_manifest.json.mac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac index c287ab32a..7c9dda943 100644 --- a/test/resources/browser_manifests/browser_manifest.json.mac +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -1,10 +1,10 @@ [ { "name":"firefox5", - "path":"/Applications/Firefox.app", + "path":"/Applications/Firefox.app" }, { "name":"firefox6", - "path":"/Users/sayrer/firefoxen/Aurora.app", + "path":"/Users/sayrer/firefoxen/Aurora.app" } ] From d97f9086bdfca9dc24f39dab79b1faa672c7cd1e Mon Sep 17 00:00:00 2001 From: Chris Jones <jones.chris.g@gmail.com> Date: Thu, 23 Jun 2011 15:36:53 -0700 Subject: [PATCH 37/45] fix rendering of some symbols. Patch by Vivien Nicolas. --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index da5926c09..ffefc61a1 100644 --- a/pdf.js +++ b/pdf.js @@ -2857,7 +2857,7 @@ var CanvasGraphics = (function() { } else { text = Fonts.charsToUnicode(text); this.ctx.translate(this.current.x, -1 * this.current.y); - this.ctx.fillText(Fonts.charsToUnicode(text), 0, 0); + this.ctx.fillText(text, 0, 0); this.current.x += this.ctx.measureText(text).width; } From ebb75998a27a0de67f00e98c8a4236129f7cdd23 Mon Sep 17 00:00:00 2001 From: Rob Sayre <sayrer@gmail.com> Date: Thu, 23 Jun 2011 15:47:25 -0700 Subject: [PATCH 38/45] only supply a -foreground argument on Mac. --- test/test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test.py b/test/test.py index d7a0fa674..53f65f78b 100644 --- a/test/test.py +++ b/test/test.py @@ -159,7 +159,10 @@ class BrowserCommand(): shutil.rmtree(self.tempDir) def start(self, url): - cmds = [self.path, "-foreground", "-no-remote", "-profile", self.profileDir, url] + cmds = [self.path] + if platform.system() == "Darwin": + cmds.append("-foreground") + cmds.extend(["-no-remote", "-profile", self.profileDir, url]) subprocess.call(cmds) def makeBrowserCommands(browserManifestFile): From ae2e637044e5abe5d4c119de133e65e0aef4a9b5 Mon Sep 17 00:00:00 2001 From: Chris Jones <jones.chris.g@gmail.com> Date: Thu, 23 Jun 2011 17:09:42 -0700 Subject: [PATCH 39/45] TODO for bpc!=8 --- pdf.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index ffefc61a1..b2b6401fd 100644 --- a/pdf.js +++ b/pdf.js @@ -3275,8 +3275,11 @@ var CanvasGraphics = (function() { } } - if (bitsPerComponent !== 8) - error("Unsupported bpc"); + if (bitsPerComponent !== 8) { + TODO("Support bpc="+ bitsPerComponent); + this.restore(); + return; + } var xref = this.xref; var colorSpaces = this.colorSpaces; From 791947ff748bf819636bf58cc43ca0e2051ebc64 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 20:50:01 -0400 Subject: [PATCH 40/45] Refactored the multi-page viewer to adhere to the coding style guidelines. --- multi-page-viewer.css | 197 ----------------- multi-page-viewer.html | 51 ----- multi-page-viewer.js | 466 ----------------------------------------- multi_page_viewer.css | 197 +++++++++++++++++ multi_page_viewer.html | 51 +++++ multi_page_viewer.js | 458 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 706 insertions(+), 714 deletions(-) delete mode 100644 multi-page-viewer.css delete mode 100644 multi-page-viewer.html delete mode 100644 multi-page-viewer.js create mode 100644 multi_page_viewer.css create mode 100644 multi_page_viewer.html create mode 100644 multi_page_viewer.js diff --git a/multi-page-viewer.css b/multi-page-viewer.css deleted file mode 100644 index 7f4701022..000000000 --- a/multi-page-viewer.css +++ /dev/null @@ -1,197 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; -} - -canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; -} - -span { - font-size: 0.8em; -} - -.control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; -} - -.control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; -} - -.control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; -} - -.page { - width: 816px; - height: 1056px; - margin: 10px auto; -} - -#controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; -} - -#controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; -} - -#previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; -} - -#previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; -} - -#nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; -} - -#nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; -} - -#openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; -} - -#openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; -} - -#openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; -} - -#fileInput { - display: none; -} - -#pageNumber { - text-align: right; -} - -#sidebar { - background-color: rgba(0, 0, 0, 0.8); - position: fixed; - width: 150px; - top: 62px; - bottom: 18px; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-radius-topright: 8px; - -moz-border-radius-bottomright: 8px; - -webkit-border-top-right-radius: 8px; - -webkit-border-bottom-right-radius: 8px; -} - -#sidebarScrollView { - position: absolute; - overflow: hidden; - overflow-y: auto; - top: 40px; - right: 10px; - bottom: 10px; - left: 10px; -} - -#sidebarContentView { - height: auto; - width: 100px; -} - -#viewer { - margin: 44px 0px 0px; - padding: 8px 0px; -} diff --git a/multi-page-viewer.html b/multi-page-viewer.html deleted file mode 100644 index ffbdfe707..000000000 --- a/multi-page-viewer.html +++ /dev/null @@ -1,51 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<title>pdf.js Multi-Page Viewer</title> -<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> -<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/> -<script type="text/javascript" src="pdf.js"></script> -<script type="text/javascript" src="fonts.js"></script> -<script type="text/javascript" src="glyphlist.js"></script> -<script type="text/javascript" src="multi-page-viewer.js"></script> -</head> -<body> - <div id="controls"> - <span class="control"> - <span id="previousPageButton" class="disabled"></span> - <span id="nextPageButton" class="disabled"></span> - <span class="label">Previous/Next</span> - </span> - <span class="control"> - <input type="text" id="pageNumber" value="1" size="2"/> - <span>/</span> - <span id="numPages">--</span> - <span class="label">Page Number</span> - </span> - <span class="control"> - <select id="scaleSelect"> - <option value="50">50%</option> - <option value="75">75%</option> - <option value="100" selected="selected">100%</option> - <option value="125">125%</option> - <option value="150">150%</option> - <option value="200">200%</option> - </select> - <span class="label">Zoom</span> - </span> - <span class="control"> - <span id="openFileButton"></span> - <input type="file" id="fileInput"/> - <span class="label">Open File</span> - </span> - </div> - <!--<div id="sidebar"> - <div id="sidebarScrollView"> - <div id="sidebarContentView"> - - </div> - </div> - </div>--> - <div id="viewer"></div> -</body> -</html> diff --git a/multi-page-viewer.js b/multi-page-viewer.js deleted file mode 100644 index baad7809e..000000000 --- a/multi-page-viewer.js +++ /dev/null @@ -1,466 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -"use strict"; - -var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, - - createPage: function(num) { - var anchor = document.createElement('a'); - anchor.name = '' + num; - - var div = document.createElement('div'); - div.id = 'pageContainer' + num; - div.className = 'page'; - div.style.width = PDFViewer.pageWidth() + 'px'; - div.style.height = PDFViewer.pageHeight() + 'px'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, - - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, - - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - PDFViewer.readPDF(data); - } - }; - - req.send(null); - }, - - readPDF: function(data) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.pdf = new PDFDoc(new Stream(data)); - PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - document.location.hash = 1; - } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } -}; - -window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - 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; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - PDFViewer.scaleSelect.onchange = function(evt) { - PDFViewer.changeScale(parseInt(this.value)); - }; - - if (window.File && window.FileReader && window.FileList && window.Blob) { - var openFileButton = document.getElementById('openFileButton'); - openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } - }; - openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - openFileButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - openFileButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.fileInput = document.getElementById('fileInput'); - PDFViewer.fileInput.onchange = function(evt) { - var files = evt.target.files; - - if (files.length > 0) { - var file = files[0]; - var fileReader = new FileReader(); - - document.title = file.name; - - // Read the local file into a Uint8Array. - fileReader.onload = function(evt) { - var data = evt.target.result; - var buffer = new ArrayBuffer(data.length); - var uint8Array = new Uint8Array(buffer); - - for (var i = 0; i < data.length; i++) { - uint8Array[i] = data.charCodeAt(i); - } - - PDFViewer.readPDF(uint8Array); - }; - - // Read as a binary string since "readAsArrayBuffer" is not yet - // implemented in Firefox. - fileReader.readAsBinaryString(file); - } - }; - PDFViewer.fileInput.value = null; - } else { - document.getElementById('fileWrapper').style.display = 'none'; - } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - - PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } - }; -}; diff --git a/multi_page_viewer.css b/multi_page_viewer.css new file mode 100644 index 000000000..fce7d7b32 --- /dev/null +++ b/multi_page_viewer.css @@ -0,0 +1,197 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +body { + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; +} + +canvas { + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; +} + +span { + font-size: 0.8em; +} + +.control { + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; +} + +.control > input { + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > span { + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; +} + +.control .label { + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; +} + +.page { + width: 816px; + height: 1056px; + margin: 10px auto; +} + +#controls { + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#controls input { + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; +} + +#previousPageButton { + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#previousPageButton.down { + background: url('images/buttons.png') no-repeat 0px -46px; +} + +#previousPageButton.disabled { + background: url('images/buttons.png') no-repeat 0px 0px; +} + +#nextPageButton { + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#nextPageButton.down { + background: url('images/buttons.png') no-repeat -28px -46px; +} + +#nextPageButton.disabled { + background: url('images/buttons.png') no-repeat -28px 0px; +} + +#openFileButton { + background: url('images/buttons.png') no-repeat -56px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px 0px 0px 3px; + width: 29px; + height: 23px; +} + +#openFileButton.down { + background: url('images/buttons.png') no-repeat -56px -46px; +} + +#openFileButton.disabled { + background: url('images/buttons.png') no-repeat -56px 0px; +} + +#fileInput { + display: none; +} + +#pageNumber { + text-align: right; +} + +#sidebar { + background-color: rgba(0, 0, 0, 0.8); + position: fixed; + width: 150px; + top: 62px; + bottom: 18px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; +} + +#sidebarScrollView { + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; +} + +#sidebarContentView { + height: auto; + width: 100px; +} + +#viewer { + margin: 44px 0px 0px; + padding: 8px 0px; +} diff --git a/multi_page_viewer.html b/multi_page_viewer.html new file mode 100644 index 000000000..47234686d --- /dev/null +++ b/multi_page_viewer.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> +<title>pdf.js Multi-Page Viewer</title> +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> +<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/> +<script type="text/javascript" src="pdf.js"></script> +<script type="text/javascript" src="fonts.js"></script> +<script type="text/javascript" src="glyphlist.js"></script> +<script type="text/javascript" src="multi_page_viewer.js"></script> +</head> +<body> + <div id="controls"> + <span class="control"> + <span id="previousPageButton" class="disabled"></span> + <span id="nextPageButton" class="disabled"></span> + <span class="label">Previous/Next</span> + </span> + <span class="control"> + <input type="text" id="pageNumber" value="1" size="2"/> + <span>/</span> + <span id="numPages">--</span> + <span class="label">Page Number</span> + </span> + <span class="control"> + <select id="scaleSelect"> + <option value="50">50%</option> + <option value="75">75%</option> + <option value="100" selected="selected">100%</option> + <option value="125">125%</option> + <option value="150">150%</option> + <option value="200">200%</option> + </select> + <span class="label">Zoom</span> + </span> + <span class="control"> + <span id="openFileButton"></span> + <input type="file" id="fileInput"/> + <span class="label">Open File</span> + </span> + </div> + <!--<div id="sidebar"> + <div id="sidebarScrollView"> + <div id="sidebarContentView"> + + </div> + </div> + </div>--> + <div id="viewer"></div> +</body> +</html> diff --git a/multi_page_viewer.js b/multi_page_viewer.js new file mode 100644 index 000000000..ddb541175 --- /dev/null +++ b/multi_page_viewer.js @@ -0,0 +1,458 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +"use strict"; + +var PDFViewer = { + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, + + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, + + createPage: function(num) { + var anchor = document.createElement('a'); + anchor.name = '' + num; + + var div = document.createElement('div'); + div.id = 'pageContainer' + num; + div.className = 'page'; + div.style.width = PDFViewer.pageWidth() + 'px'; + div.style.height = PDFViewer.pageHeight() + 'px'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; + + PDFViewer.readPDF(data); + } + }; + + req.send(null); + }, + + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); + + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + document.location.hash = 1; + } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } +}; + +window.onload = function() { + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + 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; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + PDFViewer.scaleSelect.onchange = function(evt) { + PDFViewer.changeScale(parseInt(this.value)); + }; + + if (window.File && window.FileReader && window.FileList && window.Blob) { + var openFileButton = document.getElementById('openFileButton'); + openFileButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.fileInput.click(); + } + }; + openFileButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + openFileButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + openFileButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.fileInput = document.getElementById('fileInput'); + PDFViewer.fileInput.onchange = function(evt) { + var files = evt.target.files; + + if (files.length > 0) { + var file = files[0]; + var fileReader = new FileReader(); + + document.title = file.name; + + // Read the local file into a Uint8Array. + fileReader.onload = function(evt) { + var data = evt.target.result; + var buffer = new ArrayBuffer(data.length); + var uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < data.length; i++) { + uint8Array[i] = data.charCodeAt(i); + } + + PDFViewer.readPDF(uint8Array); + }; + + // Read as a binary string since "readAsArrayBuffer" is not yet + // implemented in Firefox. + fileReader.readAsBinaryString(file); + } + }; + PDFViewer.fileInput.value = null; + } else { + document.getElementById('fileWrapper').style.display = 'none'; + } + + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; +}; From 8584daf7380bc9ef6f885a1de81dbcabd261bb5b Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 21:11:50 -0400 Subject: [PATCH 41/45] Revert ae2e637044e5abe5d4c119de133e65e0aef4a9b5^..HEAD --- multi-page-viewer.css | 197 ++++++++++ multi-page-viewer.html | 51 +++ multi-page-viewer.js | 466 +++++++++++++++++++++++ multi_page_viewer.css | 234 ++++++------ multi_page_viewer.html | 76 ++-- multi_page_viewer.js | 842 +++++++++++++++++++++-------------------- pdf.js | 7 +- 7 files changed, 1296 insertions(+), 577 deletions(-) create mode 100644 multi-page-viewer.css create mode 100644 multi-page-viewer.html create mode 100644 multi-page-viewer.js diff --git a/multi-page-viewer.css b/multi-page-viewer.css new file mode 100644 index 000000000..7f4701022 --- /dev/null +++ b/multi-page-viewer.css @@ -0,0 +1,197 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +body { + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; +} + +canvas { + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; +} + +span { + font-size: 0.8em; +} + +.control { + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; +} + +.control > input { + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > span { + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; +} + +.control .label { + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; +} + +.page { + width: 816px; + height: 1056px; + margin: 10px auto; +} + +#controls { + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#controls input { + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; +} + +#previousPageButton { + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#previousPageButton.down { + background: url('images/buttons.png') no-repeat 0px -46px; +} + +#previousPageButton.disabled { + background: url('images/buttons.png') no-repeat 0px 0px; +} + +#nextPageButton { + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#nextPageButton.down { + background: url('images/buttons.png') no-repeat -28px -46px; +} + +#nextPageButton.disabled { + background: url('images/buttons.png') no-repeat -28px 0px; +} + +#openFileButton { + background: url('images/buttons.png') no-repeat -56px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px 0px 0px 3px; + width: 29px; + height: 23px; +} + +#openFileButton.down { + background: url('images/buttons.png') no-repeat -56px -46px; +} + +#openFileButton.disabled { + background: url('images/buttons.png') no-repeat -56px 0px; +} + +#fileInput { + display: none; +} + +#pageNumber { + text-align: right; +} + +#sidebar { + background-color: rgba(0, 0, 0, 0.8); + position: fixed; + width: 150px; + top: 62px; + bottom: 18px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; +} + +#sidebarScrollView { + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; +} + +#sidebarContentView { + height: auto; + width: 100px; +} + +#viewer { + margin: 44px 0px 0px; + padding: 8px 0px; +} diff --git a/multi-page-viewer.html b/multi-page-viewer.html new file mode 100644 index 000000000..ffbdfe707 --- /dev/null +++ b/multi-page-viewer.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> +<title>pdf.js Multi-Page Viewer</title> +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> +<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/> +<script type="text/javascript" src="pdf.js"></script> +<script type="text/javascript" src="fonts.js"></script> +<script type="text/javascript" src="glyphlist.js"></script> +<script type="text/javascript" src="multi-page-viewer.js"></script> +</head> +<body> + <div id="controls"> + <span class="control"> + <span id="previousPageButton" class="disabled"></span> + <span id="nextPageButton" class="disabled"></span> + <span class="label">Previous/Next</span> + </span> + <span class="control"> + <input type="text" id="pageNumber" value="1" size="2"/> + <span>/</span> + <span id="numPages">--</span> + <span class="label">Page Number</span> + </span> + <span class="control"> + <select id="scaleSelect"> + <option value="50">50%</option> + <option value="75">75%</option> + <option value="100" selected="selected">100%</option> + <option value="125">125%</option> + <option value="150">150%</option> + <option value="200">200%</option> + </select> + <span class="label">Zoom</span> + </span> + <span class="control"> + <span id="openFileButton"></span> + <input type="file" id="fileInput"/> + <span class="label">Open File</span> + </span> + </div> + <!--<div id="sidebar"> + <div id="sidebarScrollView"> + <div id="sidebarContentView"> + + </div> + </div> + </div>--> + <div id="viewer"></div> +</body> +</html> diff --git a/multi-page-viewer.js b/multi-page-viewer.js new file mode 100644 index 000000000..baad7809e --- /dev/null +++ b/multi-page-viewer.js @@ -0,0 +1,466 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +"use strict"; + +var PDFViewer = { + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, + + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, + + createPage: function(num) { + var anchor = document.createElement('a'); + anchor.name = '' + num; + + var div = document.createElement('div'); + div.id = 'pageContainer' + num; + div.className = 'page'; + div.style.width = PDFViewer.pageWidth() + 'px'; + div.style.height = PDFViewer.pageHeight() + 'px'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || + req.mozResponse || + req.responseArrayBuffer || + req.response; + + PDFViewer.readPDF(data); + } + }; + + req.send(null); + }, + + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); + + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + document.location.hash = 1; + } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } +}; + +window.onload = function() { + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + 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; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + PDFViewer.scaleSelect.onchange = function(evt) { + PDFViewer.changeScale(parseInt(this.value)); + }; + + if (window.File && window.FileReader && window.FileList && window.Blob) { + var openFileButton = document.getElementById('openFileButton'); + openFileButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.fileInput.click(); + } + }; + openFileButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + openFileButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + openFileButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.fileInput = document.getElementById('fileInput'); + PDFViewer.fileInput.onchange = function(evt) { + var files = evt.target.files; + + if (files.length > 0) { + var file = files[0]; + var fileReader = new FileReader(); + + document.title = file.name; + + // Read the local file into a Uint8Array. + fileReader.onload = function(evt) { + var data = evt.target.result; + var buffer = new ArrayBuffer(data.length); + var uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < data.length; i++) { + uint8Array[i] = data.charCodeAt(i); + } + + PDFViewer.readPDF(uint8Array); + }; + + // Read as a binary string since "readAsArrayBuffer" is not yet + // implemented in Firefox. + fileReader.readAsBinaryString(file); + } + }; + PDFViewer.fileInput.value = null; + } else { + document.getElementById('fileWrapper').style.display = 'none'; + } + + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; +}; diff --git a/multi_page_viewer.css b/multi_page_viewer.css index fce7d7b32..7f4701022 100644 --- a/multi_page_viewer.css +++ b/multi_page_viewer.css @@ -2,196 +2,196 @@ /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; } canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; } span { - font-size: 0.8em; + font-size: 0.8em; } .control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; } .control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; } .control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; } .page { - width: 816px; - height: 1056px; - margin: 10px auto; + width: 816px; + height: 1056px; + margin: 10px auto; } #controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; } #controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; } #previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; + background: url('images/buttons.png') no-repeat 0px -46px; } #previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; + background: url('images/buttons.png') no-repeat 0px 0px; } #nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; + background: url('images/buttons.png') no-repeat -28px -46px; } #nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; + background: url('images/buttons.png') no-repeat -28px 0px; } #openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; + background: url('images/buttons.png') no-repeat -56px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px 0px 0px 3px; + width: 29px; + height: 23px; } #openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; + background: url('images/buttons.png') no-repeat -56px -46px; } #openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; + background: url('images/buttons.png') no-repeat -56px 0px; } #fileInput { - display: none; + display: none; } #pageNumber { - text-align: right; + text-align: right; } #sidebar { - background-color: rgba(0, 0, 0, 0.8); - position: fixed; - width: 150px; - top: 62px; - bottom: 18px; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-radius-topright: 8px; - -moz-border-radius-bottomright: 8px; - -webkit-border-top-right-radius: 8px; - -webkit-border-bottom-right-radius: 8px; + background-color: rgba(0, 0, 0, 0.8); + position: fixed; + width: 150px; + top: 62px; + bottom: 18px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; } #sidebarScrollView { - position: absolute; - overflow: hidden; - overflow-y: auto; - top: 40px; - right: 10px; - bottom: 10px; - left: 10px; + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; } #sidebarContentView { - height: auto; - width: 100px; + height: auto; + width: 100px; } #viewer { - margin: 44px 0px 0px; - padding: 8px 0px; + margin: 44px 0px 0px; + padding: 8px 0px; } diff --git a/multi_page_viewer.html b/multi_page_viewer.html index 47234686d..ffbdfe707 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -3,49 +3,49 @@ <head> <title>pdf.js Multi-Page Viewer</title> <meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> -<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/> +<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/> <script type="text/javascript" src="pdf.js"></script> <script type="text/javascript" src="fonts.js"></script> <script type="text/javascript" src="glyphlist.js"></script> -<script type="text/javascript" src="multi_page_viewer.js"></script> +<script type="text/javascript" src="multi-page-viewer.js"></script> </head> <body> - <div id="controls"> - <span class="control"> - <span id="previousPageButton" class="disabled"></span> - <span id="nextPageButton" class="disabled"></span> - <span class="label">Previous/Next</span> - </span> - <span class="control"> - <input type="text" id="pageNumber" value="1" size="2"/> - <span>/</span> - <span id="numPages">--</span> - <span class="label">Page Number</span> - </span> - <span class="control"> - <select id="scaleSelect"> - <option value="50">50%</option> - <option value="75">75%</option> - <option value="100" selected="selected">100%</option> - <option value="125">125%</option> - <option value="150">150%</option> - <option value="200">200%</option> - </select> - <span class="label">Zoom</span> - </span> - <span class="control"> - <span id="openFileButton"></span> - <input type="file" id="fileInput"/> - <span class="label">Open File</span> - </span> - </div> - <!--<div id="sidebar"> - <div id="sidebarScrollView"> - <div id="sidebarContentView"> - - </div> + <div id="controls"> + <span class="control"> + <span id="previousPageButton" class="disabled"></span> + <span id="nextPageButton" class="disabled"></span> + <span class="label">Previous/Next</span> + </span> + <span class="control"> + <input type="text" id="pageNumber" value="1" size="2"/> + <span>/</span> + <span id="numPages">--</span> + <span class="label">Page Number</span> + </span> + <span class="control"> + <select id="scaleSelect"> + <option value="50">50%</option> + <option value="75">75%</option> + <option value="100" selected="selected">100%</option> + <option value="125">125%</option> + <option value="150">150%</option> + <option value="200">200%</option> + </select> + <span class="label">Zoom</span> + </span> + <span class="control"> + <span id="openFileButton"></span> + <input type="file" id="fileInput"/> + <span class="label">Open File</span> + </span> </div> - </div>--> - <div id="viewer"></div> + <!--<div id="sidebar"> + <div id="sidebarScrollView"> + <div id="sidebarContentView"> + + </div> + </div> + </div>--> + <div id="viewer"></div> </body> </html> diff --git a/multi_page_viewer.js b/multi_page_viewer.js index ddb541175..baad7809e 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -4,455 +4,463 @@ "use strict"; var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, - - createPage: function(num) { - var anchor = document.createElement('a'); - anchor.name = '' + num; - - var div = document.createElement('div'); - div.id = 'pageContainer' + num; - div.className = 'page'; - div.style.width = PDFViewer.pageWidth() + 'px'; - div.style.height = PDFViewer.pageHeight() + 'px'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); + createPage: function(num) { + var anchor = document.createElement('a'); + anchor.name = '' + num; + + var div = document.createElement('div'); + div.id = 'pageContainer' + num; + div.className = 'page'; + div.style.width = PDFViewer.pageWidth() + 'px'; + div.style.height = PDFViewer.pageHeight() + 'px'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { return; - } } - clearInterval(pageInterval); + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); } - } else { - if (option.selected) { - option.removeAttribute('selected'); + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; - } - }, + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } + }, - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, - openURL: function(url) { - PDFViewer.url = url; - document.title = url; + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || + req.mozResponse || + req.responseArrayBuffer || + req.response; + + PDFViewer.readPDF(data); + } + }; - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + req.send(null); + }, - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } - PDFViewer.readPDF(data); - } - }; - - req.send(null); - }, + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - readPDF: function(data) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.pdf = new PDFDoc(new Stream(data)); - PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - document.location.hash = 1; + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + document.location.hash = 1; + } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; - } }; window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - 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; - }(); - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + 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; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - PDFViewer.scaleSelect.onchange = function(evt) { - PDFViewer.changeScale(parseInt(this.value)); - }; - - if (window.File && window.FileReader && window.FileList && window.Blob) { - var openFileButton = document.getElementById('openFileButton'); - openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } }; - openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - openFileButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - openFileButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + PDFViewer.scaleSelect.onchange = function(evt) { + PDFViewer.changeScale(parseInt(this.value)); }; - PDFViewer.fileInput = document.getElementById('fileInput'); - PDFViewer.fileInput.onchange = function(evt) { - var files = evt.target.files; - - if (files.length > 0) { - var file = files[0]; - var fileReader = new FileReader(); - - document.title = file.name; - - // Read the local file into a Uint8Array. - fileReader.onload = function(evt) { - var data = evt.target.result; - var buffer = new ArrayBuffer(data.length); - var uint8Array = new Uint8Array(buffer); - - for (var i = 0; i < data.length; i++) { - uint8Array[i] = data.charCodeAt(i); - } - - PDFViewer.readPDF(uint8Array); + if (window.File && window.FileReader && window.FileList && window.Blob) { + var openFileButton = document.getElementById('openFileButton'); + openFileButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.fileInput.click(); + } + }; + openFileButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + openFileButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + openFileButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - // Read as a binary string since "readAsArrayBuffer" is not yet - // implemented in Firefox. - fileReader.readAsBinaryString(file); - } - }; - PDFViewer.fileInput.value = null; - } else { - document.getElementById('fileWrapper').style.display = 'none'; - } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - - PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + PDFViewer.fileInput = document.getElementById('fileInput'); + PDFViewer.fileInput.onchange = function(evt) { + var files = evt.target.files; + + if (files.length > 0) { + var file = files[0]; + var fileReader = new FileReader(); + + document.title = file.name; + + // Read the local file into a Uint8Array. + fileReader.onload = function(evt) { + var data = evt.target.result; + var buffer = new ArrayBuffer(data.length); + var uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < data.length; i++) { + uint8Array[i] = data.charCodeAt(i); + } + + PDFViewer.readPDF(uint8Array); + }; + + // Read as a binary string since "readAsArrayBuffer" is not yet + // implemented in Firefox. + fileReader.readAsBinaryString(file); + } + }; + PDFViewer.fileInput.value = null; } else { - PDFViewer.willJumpToPage = false; + document.getElementById('fileWrapper').style.display = 'none'; } - }; + + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; }; diff --git a/pdf.js b/pdf.js index b2b6401fd..ffefc61a1 100644 --- a/pdf.js +++ b/pdf.js @@ -3275,11 +3275,8 @@ var CanvasGraphics = (function() { } } - if (bitsPerComponent !== 8) { - TODO("Support bpc="+ bitsPerComponent); - this.restore(); - return; - } + if (bitsPerComponent !== 8) + error("Unsupported bpc"); var xref = this.xref; var colorSpaces = this.colorSpaces; From f9687a1707adc61d17aff2533b8498bfbfedff82 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 21:12:39 -0400 Subject: [PATCH 42/45] Fixed file renaming issues. --- multi_page_viewer.css | 197 ----------------- multi_page_viewer.html | 51 ----- multi_page_viewer.js | 466 ----------------------------------------- 3 files changed, 714 deletions(-) delete mode 100644 multi_page_viewer.css delete mode 100644 multi_page_viewer.html delete mode 100644 multi_page_viewer.js diff --git a/multi_page_viewer.css b/multi_page_viewer.css deleted file mode 100644 index 7f4701022..000000000 --- a/multi_page_viewer.css +++ /dev/null @@ -1,197 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; -} - -canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; -} - -span { - font-size: 0.8em; -} - -.control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; -} - -.control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; -} - -.control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; -} - -.page { - width: 816px; - height: 1056px; - margin: 10px auto; -} - -#controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; -} - -#controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; -} - -#previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; -} - -#previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; -} - -#nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; -} - -#nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; -} - -#openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; -} - -#openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; -} - -#openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; -} - -#fileInput { - display: none; -} - -#pageNumber { - text-align: right; -} - -#sidebar { - background-color: rgba(0, 0, 0, 0.8); - position: fixed; - width: 150px; - top: 62px; - bottom: 18px; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-radius-topright: 8px; - -moz-border-radius-bottomright: 8px; - -webkit-border-top-right-radius: 8px; - -webkit-border-bottom-right-radius: 8px; -} - -#sidebarScrollView { - position: absolute; - overflow: hidden; - overflow-y: auto; - top: 40px; - right: 10px; - bottom: 10px; - left: 10px; -} - -#sidebarContentView { - height: auto; - width: 100px; -} - -#viewer { - margin: 44px 0px 0px; - padding: 8px 0px; -} diff --git a/multi_page_viewer.html b/multi_page_viewer.html deleted file mode 100644 index ffbdfe707..000000000 --- a/multi_page_viewer.html +++ /dev/null @@ -1,51 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<title>pdf.js Multi-Page Viewer</title> -<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> -<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/> -<script type="text/javascript" src="pdf.js"></script> -<script type="text/javascript" src="fonts.js"></script> -<script type="text/javascript" src="glyphlist.js"></script> -<script type="text/javascript" src="multi-page-viewer.js"></script> -</head> -<body> - <div id="controls"> - <span class="control"> - <span id="previousPageButton" class="disabled"></span> - <span id="nextPageButton" class="disabled"></span> - <span class="label">Previous/Next</span> - </span> - <span class="control"> - <input type="text" id="pageNumber" value="1" size="2"/> - <span>/</span> - <span id="numPages">--</span> - <span class="label">Page Number</span> - </span> - <span class="control"> - <select id="scaleSelect"> - <option value="50">50%</option> - <option value="75">75%</option> - <option value="100" selected="selected">100%</option> - <option value="125">125%</option> - <option value="150">150%</option> - <option value="200">200%</option> - </select> - <span class="label">Zoom</span> - </span> - <span class="control"> - <span id="openFileButton"></span> - <input type="file" id="fileInput"/> - <span class="label">Open File</span> - </span> - </div> - <!--<div id="sidebar"> - <div id="sidebarScrollView"> - <div id="sidebarContentView"> - - </div> - </div> - </div>--> - <div id="viewer"></div> -</body> -</html> diff --git a/multi_page_viewer.js b/multi_page_viewer.js deleted file mode 100644 index baad7809e..000000000 --- a/multi_page_viewer.js +++ /dev/null @@ -1,466 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -"use strict"; - -var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, - - createPage: function(num) { - var anchor = document.createElement('a'); - anchor.name = '' + num; - - var div = document.createElement('div'); - div.id = 'pageContainer' + num; - div.className = 'page'; - div.style.width = PDFViewer.pageWidth() + 'px'; - div.style.height = PDFViewer.pageHeight() + 'px'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, - - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, - - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - PDFViewer.readPDF(data); - } - }; - - req.send(null); - }, - - readPDF: function(data) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.pdf = new PDFDoc(new Stream(data)); - PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - document.location.hash = 1; - } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } -}; - -window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - 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; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - PDFViewer.scaleSelect.onchange = function(evt) { - PDFViewer.changeScale(parseInt(this.value)); - }; - - if (window.File && window.FileReader && window.FileList && window.Blob) { - var openFileButton = document.getElementById('openFileButton'); - openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } - }; - openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - openFileButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - openFileButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.fileInput = document.getElementById('fileInput'); - PDFViewer.fileInput.onchange = function(evt) { - var files = evt.target.files; - - if (files.length > 0) { - var file = files[0]; - var fileReader = new FileReader(); - - document.title = file.name; - - // Read the local file into a Uint8Array. - fileReader.onload = function(evt) { - var data = evt.target.result; - var buffer = new ArrayBuffer(data.length); - var uint8Array = new Uint8Array(buffer); - - for (var i = 0; i < data.length; i++) { - uint8Array[i] = data.charCodeAt(i); - } - - PDFViewer.readPDF(uint8Array); - }; - - // Read as a binary string since "readAsArrayBuffer" is not yet - // implemented in Firefox. - fileReader.readAsBinaryString(file); - } - }; - PDFViewer.fileInput.value = null; - } else { - document.getElementById('fileWrapper').style.display = 'none'; - } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - - PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } - }; -}; From b18006a055b07eefc6b1495c7a581c1fa55681b5 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 21:17:31 -0400 Subject: [PATCH 43/45] Fixed file renaming issues. --- multi-page-viewer.css => multi_page_viewer.css | 0 multi-page-viewer.html => multi_page_viewer.html | 0 multi-page-viewer.js => multi_page_viewer.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename multi-page-viewer.css => multi_page_viewer.css (100%) rename multi-page-viewer.html => multi_page_viewer.html (100%) rename multi-page-viewer.js => multi_page_viewer.js (100%) diff --git a/multi-page-viewer.css b/multi_page_viewer.css similarity index 100% rename from multi-page-viewer.css rename to multi_page_viewer.css diff --git a/multi-page-viewer.html b/multi_page_viewer.html similarity index 100% rename from multi-page-viewer.html rename to multi_page_viewer.html diff --git a/multi-page-viewer.js b/multi_page_viewer.js similarity index 100% rename from multi-page-viewer.js rename to multi_page_viewer.js From b9af0da8f8bd59874b1141649b5b84dc30b7ca71 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 21:18:07 -0400 Subject: [PATCH 44/45] Fixed vim indentation rules. --- multi_page_viewer.css | 238 ++++++------ multi_page_viewer.html | 76 ++-- multi_page_viewer.js | 846 ++++++++++++++++++++--------------------- 3 files changed, 576 insertions(+), 584 deletions(-) diff --git a/multi_page_viewer.css b/multi_page_viewer.css index 7f4701022..b3eaab792 100644 --- a/multi_page_viewer.css +++ b/multi_page_viewer.css @@ -1,197 +1,197 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; } canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; } span { - font-size: 0.8em; + font-size: 0.8em; } .control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; } .control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; } .control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; } .page { - width: 816px; - height: 1056px; - margin: 10px auto; + width: 816px; + height: 1056px; + margin: 10px auto; } #controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; } #controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; } #previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; + background: url('images/buttons.png') no-repeat 0px -46px; } #previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; + background: url('images/buttons.png') no-repeat 0px 0px; } #nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; + background: url('images/buttons.png') no-repeat -28px -46px; } #nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; + background: url('images/buttons.png') no-repeat -28px 0px; } #openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; + background: url('images/buttons.png') no-repeat -56px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px 0px 0px 3px; + width: 29px; + height: 23px; } #openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; + background: url('images/buttons.png') no-repeat -56px -46px; } #openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; + background: url('images/buttons.png') no-repeat -56px 0px; } #fileInput { - display: none; + display: none; } #pageNumber { - text-align: right; + text-align: right; } #sidebar { - background-color: rgba(0, 0, 0, 0.8); - position: fixed; - width: 150px; - top: 62px; - bottom: 18px; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-radius-topright: 8px; - -moz-border-radius-bottomright: 8px; - -webkit-border-top-right-radius: 8px; - -webkit-border-bottom-right-radius: 8px; + background-color: rgba(0, 0, 0, 0.8); + position: fixed; + width: 150px; + top: 62px; + bottom: 18px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; } #sidebarScrollView { - position: absolute; - overflow: hidden; - overflow-y: auto; - top: 40px; - right: 10px; - bottom: 10px; - left: 10px; + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; } #sidebarContentView { - height: auto; - width: 100px; + height: auto; + width: 100px; } #viewer { - margin: 44px 0px 0px; - padding: 8px 0px; + margin: 44px 0px 0px; + padding: 8px 0px; } diff --git a/multi_page_viewer.html b/multi_page_viewer.html index ffbdfe707..47234686d 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -3,49 +3,49 @@ <head> <title>pdf.js Multi-Page Viewer</title> <meta http-equiv="Content-type" content="text/html;charset=UTF-8"/> -<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/> +<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/> <script type="text/javascript" src="pdf.js"></script> <script type="text/javascript" src="fonts.js"></script> <script type="text/javascript" src="glyphlist.js"></script> -<script type="text/javascript" src="multi-page-viewer.js"></script> +<script type="text/javascript" src="multi_page_viewer.js"></script> </head> <body> - <div id="controls"> - <span class="control"> - <span id="previousPageButton" class="disabled"></span> - <span id="nextPageButton" class="disabled"></span> - <span class="label">Previous/Next</span> - </span> - <span class="control"> - <input type="text" id="pageNumber" value="1" size="2"/> - <span>/</span> - <span id="numPages">--</span> - <span class="label">Page Number</span> - </span> - <span class="control"> - <select id="scaleSelect"> - <option value="50">50%</option> - <option value="75">75%</option> - <option value="100" selected="selected">100%</option> - <option value="125">125%</option> - <option value="150">150%</option> - <option value="200">200%</option> - </select> - <span class="label">Zoom</span> - </span> - <span class="control"> - <span id="openFileButton"></span> - <input type="file" id="fileInput"/> - <span class="label">Open File</span> - </span> + <div id="controls"> + <span class="control"> + <span id="previousPageButton" class="disabled"></span> + <span id="nextPageButton" class="disabled"></span> + <span class="label">Previous/Next</span> + </span> + <span class="control"> + <input type="text" id="pageNumber" value="1" size="2"/> + <span>/</span> + <span id="numPages">--</span> + <span class="label">Page Number</span> + </span> + <span class="control"> + <select id="scaleSelect"> + <option value="50">50%</option> + <option value="75">75%</option> + <option value="100" selected="selected">100%</option> + <option value="125">125%</option> + <option value="150">150%</option> + <option value="200">200%</option> + </select> + <span class="label">Zoom</span> + </span> + <span class="control"> + <span id="openFileButton"></span> + <input type="file" id="fileInput"/> + <span class="label">Open File</span> + </span> + </div> + <!--<div id="sidebar"> + <div id="sidebarScrollView"> + <div id="sidebarContentView"> + + </div> </div> - <!--<div id="sidebar"> - <div id="sidebarScrollView"> - <div id="sidebarContentView"> - - </div> - </div> - </div>--> - <div id="viewer"></div> + </div>--> + <div id="viewer"></div> </body> </html> diff --git a/multi_page_viewer.js b/multi_page_viewer.js index baad7809e..3a02ea332 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -1,466 +1,458 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ "use strict"; var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, + + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, - createPage: function(num) { - var anchor = document.createElement('a'); - anchor.name = '' + num; - - var div = document.createElement('div'); - div.id = 'pageContainer' + num; - div.className = 'page'; - div.style.width = PDFViewer.pageWidth() + 'px'; - div.style.height = PDFViewer.pageHeight() + 'px'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, + createPage: function(num) { + var anchor = document.createElement('a'); + anchor.name = '' + num; + + var div = document.createElement('div'); + div.id = 'pageContainer' + num; + div.className = 'page'; + div.style.width = PDFViewer.pageWidth() + 'px'; + div.style.height = PDFViewer.pageHeight() + 'px'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; } - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); + new Font(font.name, font.file, font.properties); - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } - // Inspect fonts and translate the missing one - var fontCount = fonts.length; + clearInterval(pageInterval); - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); } - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); + } else { + if (option.selected) { + option.removeAttribute('selected'); } - }, + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - PDFViewer.readPDF(data); - } - }; + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; - req.send(null); - }, + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - readPDF: function(data) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; - PDFViewer.pdf = new PDFDoc(new Stream(data)); - PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); + PDFViewer.readPDF(data); + } + }; + + req.send(null); + }, - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - document.location.hash = 1; - } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); } + + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); + + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + document.location.hash = 1; + } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } }; window.onload = function() { + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + 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; + }(); - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - 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; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + PDFViewer.scaleSelect.onchange = function(evt) { + PDFViewer.changeScale(parseInt(this.value)); + }; + + if (window.File && window.FileReader && window.FileList && window.Blob) { + var openFileButton = document.getElementById('openFileButton'); + openFileButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.fileInput.click(); + } }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + openFileButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + openFileButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - PDFViewer.scaleSelect.onchange = function(evt) { - PDFViewer.changeScale(parseInt(this.value)); + openFileButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - if (window.File && window.FileReader && window.FileList && window.Blob) { - var openFileButton = document.getElementById('openFileButton'); - openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } - }; - openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - openFileButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - openFileButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; + PDFViewer.fileInput = document.getElementById('fileInput'); + PDFViewer.fileInput.onchange = function(evt) { + var files = evt.target.files; + + if (files.length > 0) { + var file = files[0]; + var fileReader = new FileReader(); - PDFViewer.fileInput = document.getElementById('fileInput'); - PDFViewer.fileInput.onchange = function(evt) { - var files = evt.target.files; - - if (files.length > 0) { - var file = files[0]; - var fileReader = new FileReader(); - - document.title = file.name; - - // Read the local file into a Uint8Array. - fileReader.onload = function(evt) { - var data = evt.target.result; - var buffer = new ArrayBuffer(data.length); - var uint8Array = new Uint8Array(buffer); - - for (var i = 0; i < data.length; i++) { - uint8Array[i] = data.charCodeAt(i); - } - - PDFViewer.readPDF(uint8Array); - }; - - // Read as a binary string since "readAsArrayBuffer" is not yet - // implemented in Firefox. - fileReader.readAsBinaryString(file); - } - }; - PDFViewer.fileInput.value = null; - } else { - document.getElementById('fileWrapper').style.display = 'none'; - } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - - PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); + document.title = file.name; - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + // Read the local file into a Uint8Array. + fileReader.onload = function(evt) { + var data = evt.target.result; + var buffer = new ArrayBuffer(data.length); + var uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < data.length; i++) { + uint8Array[i] = data.charCodeAt(i); + } + + PDFViewer.readPDF(uint8Array); + }; - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } + // Read as a binary string since "readAsArrayBuffer" is not yet + // implemented in Firefox. + fileReader.readAsBinaryString(file); + } }; + PDFViewer.fileInput.value = null; + } else { + document.getElementById('fileWrapper').style.display = 'none'; + } + + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; }; From c5b889a47baad3a581cda3861c4137a1f63f8943 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo <justindarc@gmail.com> Date: Thu, 23 Jun 2011 21:37:40 -0400 Subject: [PATCH 45/45] Brought pdf.js back up to the latest revision. --- pdf.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index ffefc61a1..b2b6401fd 100644 --- a/pdf.js +++ b/pdf.js @@ -3275,8 +3275,11 @@ var CanvasGraphics = (function() { } } - if (bitsPerComponent !== 8) - error("Unsupported bpc"); + if (bitsPerComponent !== 8) { + TODO("Support bpc="+ bitsPerComponent); + this.restore(); + return; + } var xref = this.xref; var colorSpaces = this.colorSpaces;