diff --git a/Makefile b/Makefile index d32c1a252..62565670a 100644 --- a/Makefile +++ b/Makefile @@ -210,6 +210,7 @@ pages-repo: | $(BUILD_DIR) # copy of the pdf.js source. CONTENT_DIR := content BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'` +PDFJSSCRIPT_VERSION := 0.2.$(BUILD_NUMBER) EXTENSION_WEB_FILES = \ web/images \ web/viewer.css \ @@ -225,14 +226,28 @@ FIREFOX_CONTENT_DIR := $(EXTENSION_SRC)/firefox/$(CONTENT_DIR)/ FIREFOX_EXTENSION_FILES_TO_COPY = \ *.js \ *.rdf \ + *.png \ + install.rdf.in \ + README.mozilla \ components \ + ../../LICENSE \ $(NULL) FIREFOX_EXTENSION_FILES = \ - content \ - *.js \ + bootstrap.js \ install.rdf \ + icon.png \ + icon64.png \ components \ content \ + LICENSE \ + $(NULL) +FIREFOX_MC_EXTENSION_FILES = \ + bootstrap.js \ + icon.png \ + icon64.png \ + components \ + content \ + LICENSE \ $(NULL) CHROME_BUILD_DIR := $(BUILD_DIR)/chrome @@ -265,9 +280,12 @@ extension: | production # We don't need pdf.js anymore since its inlined @rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/; # Update the build version number - @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf - @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf.in + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/update.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/README.mozilla @rm -f $(FIREFOX_BUILD_DIR)/*.bak + @find $(FIREFOX_BUILD_DIR) -name ".*" -delete # Create the xpi @cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES) @echo "extension created: " $(FIREFOX_EXTENSION_NAME) @@ -276,6 +294,8 @@ extension: | production @rm -f $(FIREFOX_BUILD_DIR)/*.bak @cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_AMO_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES) @echo "AMO extension created: " $(FIREFOX_AMO_EXTENSION_NAME) + # List all files for mozilla-central + @cd $(FIREFOX_BUILD_DIR); find $(FIREFOX_MC_EXTENSION_FILES) -type f > extension-files # Clear out everything in the chrome extension build directory @rm -Rf $(CHROME_BUILD_DIR) diff --git a/README.md b/README.md index c9631df61..51a7f4e98 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PDF.JS - + pdf.js is an HTML5 technology experiment that explores building a faithful and efficient Portable Document Format (PDF) renderer without native code assistance. diff --git a/extensions/chrome/pdfHandler.html b/extensions/chrome/pdfHandler.html index 1d213bbba..fc67c581d 100644 --- a/extensions/chrome/pdfHandler.html +++ b/extensions/chrome/pdfHandler.html @@ -1,18 +1,28 @@ diff --git a/extensions/firefox/README.mozilla b/extensions/firefox/README.mozilla new file mode 100644 index 000000000..a7be57a63 --- /dev/null +++ b/extensions/firefox/README.mozilla @@ -0,0 +1,4 @@ +This is the pdf.js project output, https://github.com/mozilla/pdf.js + +Current extension version is: PDFJSSCRIPT_VERSION + diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index dc9d51f36..d03812bcb 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -10,10 +10,14 @@ let Cc = Components.classes; let Ci = Components.interfaces; let Cm = Components.manager; let Cu = Components.utils; +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); Cu.import('resource://gre/modules/Services.jsm'); function log(str) { + if (!application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false)) + return; dump(str + '\n'); } @@ -60,12 +64,7 @@ function startup(aData, aReason) { var ioService = Services.io; var resProt = ioService.getProtocolHandler('resource') .QueryInterface(Ci.nsIResProtocolHandler); - var aliasFile = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - var componentPath = aData.installPath.clone(); - componentPath.append('content'); - aliasFile.initWithPath(componentPath.path); - var aliasURI = ioService.newFileURI(aliasFile); + var aliasURI = ioService.newURI('content/', 'UTF-8', aData.resourceURI); resProt.setSubstitution(RESOURCE_NAME, aliasURI); // Load the component and register it. @@ -73,12 +72,11 @@ function startup(aData, aReason) { 'components/PdfStreamConverter.js'; Cu.import(pdfStreamConverterUrl); Factory.register(PdfStreamConverter); - Services.prefs.setBoolPref('extensions.pdf.js.active', true); } function shutdown(aData, aReason) { - if (Services.prefs.getBoolPref('extensions.pdf.js.active')) - Services.prefs.setBoolPref('extensions.pdf.js.active', false); + if (aReason == APP_SHUTDOWN) + return; var ioService = Services.io; var resProt = ioService.getProtocolHandler('resource') .QueryInterface(Ci.nsIResProtocolHandler); @@ -87,18 +85,14 @@ function shutdown(aData, aReason) { // Remove the contract/component. Factory.unregister(); // Unload the converter - if (pdfStreamConverterUrl) { - Cu.unload(pdfStreamConverterUrl); - pdfStreamConverterUrl = null; - } + Cu.unload(pdfStreamConverterUrl); + pdfStreamConverterUrl = null; } function install(aData, aReason) { - Services.prefs.setBoolPref('extensions.pdf.js.active', false); } function uninstall(aData, aReason) { - Services.prefs.clearUserPref('extensions.pdf.js.active'); application.prefs.setValue(EXT_PREFIX + '.database', '{}'); } diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index c6dab9ba7..9375a2690 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -17,7 +17,15 @@ const MAX_DATABASE_LENGTH = 4096; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); +let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + function log(aMsg) { + if (!application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false)) + return; let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); Services.console.logStringMessage(msg); dump(msg + '\n'); @@ -40,11 +48,6 @@ function topWindow(win) { .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); } -let application = Cc['@mozilla.org/fuel/application;1'] - .getService(Ci.fuelIApplication); -let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] - .getService(Ci.nsIPrivateBrowsingService); -let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; // All the priviledged actions. function ChromeActions() { @@ -66,6 +69,9 @@ ChromeActions.prototype = { if (this.inPrivateBrowswing) return '{}'; return application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + }, + pdfBugEnabled: function() { + return application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false); } }; @@ -122,15 +128,12 @@ PdfStreamConverter.prototype = { // nsIStreamConverter::asyncConvertData asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { - if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - // Ignoring HTTP POST requests -- pdf.js has to repeat the request. var skipConversion = false; try { var request = aCtxt; request.QueryInterface(Ci.nsIHttpChannel); - skipConversion = (request.requestMethod === 'POST'); + skipConversion = (request.requestMethod !== 'GET'); } catch (e) { // Non-HTTP request... continue normally. } diff --git a/extensions/firefox/icon.png b/extensions/firefox/icon.png new file mode 100644 index 000000000..64763756e Binary files /dev/null and b/extensions/firefox/icon.png differ diff --git a/extensions/firefox/icon64.png b/extensions/firefox/icon64.png new file mode 100644 index 000000000..0131bbb5c Binary files /dev/null and b/extensions/firefox/icon64.png differ diff --git a/extensions/firefox/install.rdf b/extensions/firefox/install.rdf index dd86b3245..0a0d813b2 100644 --- a/extensions/firefox/install.rdf +++ b/extensions/firefox/install.rdf @@ -5,20 +5,18 @@ uriloader@pdf.js - pdf.js - 0.2.PDFJSSCRIPT_BUILD - chrome://pdf.js/skin/logo.png + PDF Viewer + PDFJSSCRIPT_VERSION {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 6.0 - 13.0a1 + 14.0a1 true - true - Mozilla Labs - pdf.js uri loader + Mozilla + Uses HTML5 to display PDF files directly in Firefox. https://github.com/mozilla/pdf.js/ 2 diff --git a/extensions/firefox/install.rdf.in b/extensions/firefox/install.rdf.in new file mode 100644 index 000000000..3f8f87cda --- /dev/null +++ b/extensions/firefox/install.rdf.in @@ -0,0 +1,26 @@ + + +#filter substitution + + + + + uriloader@pdf.js + PDF Viewer + PDFJSSCRIPT_VERSION + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + @FIREFOX_VERSION@ + @FIREFOX_VERSION@ + + + true + true + Mozilla + Uses HTML5 to display PDF files directly in Firefox. + http://support.mozilla.org/kb/using-mozilla-pdf-viewer + 2 + + diff --git a/extensions/firefox/update.rdf b/extensions/firefox/update.rdf index c12e53dc6..b31051b5f 100644 --- a/extensions/firefox/update.rdf +++ b/extensions/firefox/update.rdf @@ -8,7 +8,7 @@ - 0.2.PDFJSSCRIPT_BUILD + PDFJSSCRIPT_VERSION {ec8030f7-c20a-464f-9b0e-13a3a9e97384} diff --git a/external/shelljs/README.md b/external/shelljs/README.md index 01b99a5f8..82f53533b 100644 --- a/external/shelljs/README.md +++ b/external/shelljs/README.md @@ -1,8 +1,10 @@ # ShellJS - Unix shell commands for Node.js [![Build Status](https://secure.travis-ci.org/arturadib/shelljs.png)](http://travis-ci.org/arturadib/shelljs) +_This project is young and experimental. Use at your own risk._ + ShellJS is a **portable** (Windows included) implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script's dependency on Unix while still keeping its familiar and powerful commands. -The project is both [unit-tested](http://travis-ci.org/arturadib/shelljs) and battle-tested at Mozilla's [pdf.js](http://github.com/mozilla/pdf.js). +The project is [unit-tested](http://travis-ci.org/arturadib/shelljs) and is being used at Mozilla's [pdf.js](http://github.com/mozilla/pdf.js). ### Example @@ -12,7 +14,7 @@ require('shelljs/global'); // Copy files to release dir mkdir('-p', 'out/Release'); -cp('-R', 'lib/*.js', 'out/Release'); +cp('-R', 'stuff/*', 'out/Release'); // Replace macros in each .js file cd('lib'); @@ -130,6 +132,27 @@ Returns list of files in the given path, or in current directory if no path prov For convenient iteration via `for (file in ls())`, the format returned is a hash object: `{ 'file1':null, 'dir1/file2':null, ...}`. +#### find(path [,path ...]) +#### find(path_array) +Examples: + +```javascript +find('src', 'lib'); +find(['src', 'lib']); // same as above +for (file in find('.')) { +if (!file.match(/\.js$/)) +continue; +// all files at this point end in '.js' +} +``` + +Returns list of all files (however deep) in the given paths. For convenient iteration +via `for (file in find(...))`, the format returned is a hash object: +`{ 'file1':null, 'dir1/file2':null, ...}`. + +The main difference with respect to `ls('-R', path)` is that the resulting file names +include the base directories, e.g. `lib/resources/file1` instead of just `file1`. + #### cp('[options ,] source [,source ...], dest') #### cp('[options ,] source_array, dest') Available options: @@ -195,6 +218,21 @@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above Creates directories. +#### test(expression) +Available expression primaries: + ++ `'-d', 'path'`: true if path is a directory ++ `'-f', 'path'`: true if path is a regular file + +Examples: + +```javascript +if (test('-d', path)) { /* do something with dir */ }; +if (!test('-f', path)) continue; // skip if it's a regular file +``` + +Evaluates expression using the available primaries and returns corresponding value. + #### cat(file [, file ...]) #### cat(file_array) diff --git a/external/shelljs/shell.js b/external/shelljs/shell.js index 2d9a5a787..95ddaa067 100644 --- a/external/shelljs/shell.js +++ b/external/shelljs/shell.js @@ -57,6 +57,7 @@ function _pwd(options) { }; exports.pwd = wrap('pwd', _pwd); + //@ //@ #### ls([options ,] path [,path ...]) //@ #### ls([options ,] path_array) @@ -159,6 +160,54 @@ function _ls(options, paths) { exports.ls = wrap('ls', _ls); +//@ +//@ #### find(path [,path ...]) +//@ #### find(path_array) +//@ Examples: +//@ +//@ ```javascript +//@ find('src', 'lib'); +//@ find(['src', 'lib']); // same as above +//@ for (file in find('.')) { +//@ if (!file.match(/\.js$/)) +//@ continue; +//@ // all files at this point end in '.js' +//@ } +//@ ``` +//@ +//@ Returns list of all files (however deep) in the given paths. For convenient iteration +//@ via `for (file in find(...))`, the format returned is a hash object: +//@ `{ 'file1':null, 'dir1/file2':null, ...}`. +//@ +//@ The main difference from `ls('-R', path)` is that the resulting file names +//@ include the base directories, e.g. `lib/resources/file1` instead of just `file1`. +function _find(options, paths) { + if (!paths) + error('no path specified'); + else if (typeof paths === 'object') + paths = paths; // assume array + else if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + + var hash = {}; + + // why not simply do ls('-R', paths)? because the output wouldn't give the base dirs + // to get the base dir in the output, we need instead ls('-R', 'dir/*') for every directory + + paths.forEach(function(file){ + hash[file] = null; + + if (fs.statSync(file).isDirectory()) { + for (subfile in _ls('-Ra', file+'/*')) + hash[subfile] = null; + } + }); + + return hash; +} +exports.find = wrap('find', _find); + + //@ //@ #### cp('[options ,] source [,source ...], dest') //@ #### cp('[options ,] source_array, dest') @@ -438,6 +487,42 @@ function _mkdir(options, dirs) { }; // mkdir exports.mkdir = wrap('mkdir', _mkdir); +//@ +//@ #### test(expression) +//@ Available expression primaries: +//@ +//@ + `'-d', 'path'`: true if path is a directory +//@ + `'-f', 'path'`: true if path is a regular file +//@ +//@ Examples: +//@ +//@ ```javascript +//@ if (test('-d', path)) { /* do something with dir */ }; +//@ if (!test('-f', path)) continue; // skip if it's a regular file +//@ ``` +//@ +//@ Evaluates expression using the available primaries and returns corresponding value. +function _test(options, path) { + if (!path) + error('no path given'); + + // hack - only works with unary primaries + options = parseOptions(options, { + 'd': 'directory', + 'f': 'file' + }); + if (!options.directory && !options.file) + error('could not interpret expression'); + + if (options.directory) + return fs.existsSync(path) && fs.statSync(path).isDirectory(); + + if (options.file) + return fs.existsSync(path) && fs.statSync(path).isFile(); +}; // test +exports.test = wrap('test', _test); + + //@ //@ #### cat(file [, file ...]) //@ #### cat(file_array) diff --git a/make.js b/make.js index 7c514a34a..33771aeb7 100755 --- a/make.js +++ b/make.js @@ -149,7 +149,7 @@ target.pagesrepo = function() { echo(); echo('Cloning project repo...'); echo('(This operation can take a while, depending on network conditions)'); - exec('git clone -b gh-pages --depth=1 ' + REPO + ' ' + ßGH_PAGES_DIR, + exec('git clone -b gh-pages --depth=1 ' + REPO + ' ' + GH_PAGES_DIR, {silent: true}); echo('Done.'); } @@ -168,13 +168,16 @@ target.pagesrepo = function() { // var EXTENSION_WEB_FILES = - ['web/images', + ['web/debugger.js', + 'web/images', 'web/viewer.css', 'web/viewer.js', 'web/viewer.html', 'web/viewer-production.html'], EXTENSION_BASE_VERSION = '4bb289ec499013de66eb421737a4dbb4a9273eda', - EXTENSION_BUILD_NUMBER; + EXTENSION_VERSION_PREFIX = '0.2.', + EXTENSION_BUILD_NUMBER, + EXTENSION_VERSION; // // make extension @@ -200,6 +203,8 @@ target.buildnumber = function() { .output.match(/\n/g).length; // get # of lines in git output echo('Extension build number: ' + EXTENSION_BUILD_NUMBER); + + EXTENSION_VERSION = EXTENSION_VERSION_PREFIX + EXTENSION_BUILD_NUMBER; }; // @@ -215,13 +220,26 @@ target.firefox = function() { FIREFOX_EXTENSION_FILES_TO_COPY = ['*.js', '*.rdf', - 'components']; - FIREFOX_EXTENSION_FILES = - ['content', - '*.js', - 'install.rdf', + '*.png', + 'install.rdf.in', + 'README.mozilla', 'components', - 'content']; + '../../LICENSE']; + FIREFOX_EXTENSION_FILES = + ['bootstrap.js', + 'install.rdf', + 'icon.png', + 'icon64.png', + 'components', + 'content', + 'LICENSE']; + FIREFOX_MC_EXTENSION_FILES = + ['bootstrap.js', + 'icon.png', + 'icon64.png', + 'components', + 'content', + 'LICENSE']; FIREFOX_EXTENSION_NAME = 'pdf.js.xpi', FIREFOX_AMO_EXTENSION_NAME = 'pdf.js.amo.xpi'; @@ -258,10 +276,17 @@ target.firefox = function() { // We don't need pdf.js anymore since its inlined rm('-Rf', FIREFOX_BUILD_CONTENT_DIR + BUILD_DIR); + // Remove '.DS_Store' and other hidden files + for (file in find(FIREFOX_BUILD_DIR)) { + if (file.match(/^\./)) + rm('-f', file); + } // Update the build version number - sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR + '/install.rdf'); - sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR + '/update.rdf'); + sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/install.rdf'); + sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/update.rdf'); + sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/install.rdf.in'); + sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/README.mozilla'); // Create the xpi cd(FIREFOX_BUILD_DIR); @@ -275,6 +300,15 @@ target.firefox = function() { exec('zip -r ' + FIREFOX_AMO_EXTENSION_NAME + ' ' + FIREFOX_EXTENSION_FILES.join(' ')); echo('AMO extension created: ' + FIREFOX_AMO_EXTENSION_NAME); cd(ROOT_DIR); + + // List all files for mozilla-central + cd(FIREFOX_BUILD_DIR); + var extensionFiles = ''; + for (file in find(FIREFOX_MC_EXTENSION_FILES)) { + if (test('-f', file)) + extensionFiles += file+'\n'; + } + extensionFiles.to('extension-files'); }; // diff --git a/src/bidi.js b/src/bidi.js index a6e3e429f..aab477dbc 100644 --- a/src/bidi.js +++ b/src/bidi.js @@ -1,433 +1,436 @@ -/* -*- 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 bidi = PDFJS.bidi = (function bidiClosure() { - // Character types for symbols from 0000 to 00FF. - var baseTypes = [ - 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS', - 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', - 'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON', - 'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN', - 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON', - 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', - 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN', - 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', - 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', - 'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON', - 'ON', 'ON', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON', - 'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', - 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L' - ]; - - // Character types for symbols from 0600 to 06FF - var arabicTypes = [ - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', - 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', - 'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', - 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'ON', 'NSM', - 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', - 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL' - ]; - - function isOdd(i) { - return (i & 1) != 0; - } - - function isEven(i) { - return (i & 1) == 0; - } - - function findUnequal(arr, start, value) { - var j; - for (var j = start, jj = arr.length; j < jj; ++j) { - if (arr[j] != value) - return j; - } - return j; - } - - function setValues(arr, start, end, value) { - for (var j = start; j < end; ++j) { - arr[j] = value; - } - } - - function reverseValues(arr, start, end) { - for (var i = start, j = end - 1; i < j; ++i, --j) { - var temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } - } - - function mirrorGlyphs(c) { - /* - # BidiMirroring-1.txt - 0028; 0029 # LEFT PARENTHESIS - 0029; 0028 # RIGHT PARENTHESIS - 003C; 003E # LESS-THAN SIGN - 003E; 003C # GREATER-THAN SIGN - 005B; 005D # LEFT SQUARE BRACKET - 005D; 005B # RIGHT SQUARE BRACKET - 007B; 007D # LEFT CURLY BRACKET - 007D; 007B # RIGHT CURLY BRACKET - 00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - 00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - */ - switch (c) { - case '(': - return ')'; - case ')': - return '('; - case '<': - return '>'; - case '>': - return '<'; - case ']': - return '['; - case '[': - return ']'; - case '}': - return '{'; - case '{': - return '}'; - case '\u00AB': - return '\u00BB'; - case '\u00BB': - return '\u00AB'; - default: - return c; - } - } - - return (function bidi(text, startLevel) { - var str = text.str; - var strLength = str.length; - if (strLength == 0) - return str; - - // get types, fill arrays - - var chars = new Array(strLength); - var types = new Array(strLength); - var oldtypes = new Array(strLength); - var numBidi = 0; - - for (var i = 0; i < strLength; ++i) { - chars[i] = str.charAt(i); - - var charCode = str.charCodeAt(i); - var charType = 'L'; - if (charCode <= 0x00ff) - charType = baseTypes[charCode]; - else if (0x0590 <= charCode && charCode <= 0x05f4) - charType = 'R'; - else if (0x0600 <= charCode && charCode <= 0x06ff) - charType = arabicTypes[charCode & 0xff]; - else if (0x0700 <= charCode && charCode <= 0x08AC) - charType = 'AL'; - - if (charType == 'R' || charType == 'AL' || charType == 'AN') - numBidi++; - - oldtypes[i] = types[i] = charType; - } - - // detect the bidi method - // if there are no rtl characters then no bidi needed - // if less than 30% chars are rtl then string is primarily ltr - // if more than 30% chars are rtl then string is primarily rtl - if (numBidi == 0) { - text.direction = 'ltr'; - return str; - } - - if (startLevel == -1) { - if ((strLength / numBidi) < 0.3) { - text.direction = 'ltr'; - startLevel = 0; - } else { - text.direction = 'rtl'; - startLevel = 1; - } - } - - var levels = new Array(strLength); - - for (var i = 0; i < strLength; ++i) { - levels[i] = startLevel; - } - - var diffChars = new Array(strLength); - var diffLevels = new Array(strLength); - var diffTypes = new Array(strLength); - - /* - X1-X10: skip most of this, since we are NOT doing the embeddings. - */ - - var e = isOdd(startLevel) ? 'R' : 'L'; - var sor = e; - var eor = sor; - - /* - W1. Examine each non-spacing mark (NSM) in the level run, and change the - type of the NSM to the type of the previous character. If the NSM is at the - start of the level run, it will get the type of sor. - */ - - var lastType = sor; - for (var i = 0; i < strLength; ++i) { - if (types[i] == 'NSM') - types[i] = lastType; - else - lastType = types[i]; - } - - /* - W2. Search backwards from each instance of a European number until the - first strong type (R, L, AL, or sor) is found. If an AL is found, change - the type of the European number to Arabic number. - */ - - var lastType = sor; - for (var i = 0; i < strLength; ++i) { - var t = types[i]; - if (t == 'EN') - types[i] = (lastType == 'AL') ? 'AN' : 'EN'; - else if (t == 'R' || t == 'L' || t == 'AL') - lastType = t; - } - - /* - W3. Change all ALs to R. - */ - - for (var i = 0; i < strLength; ++i) { - var t = types[i]; - if (t == 'AL') - types[i] = 'R'; - } - - /* - W4. A single European separator between two European numbers changes to a - European number. A single common separator between two numbers of the same - type changes to that type: - */ - - for (var i = 1; i < strLength - 1; ++i) { - if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') - types[i] = 'EN'; - if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') && - types[i + 1] == types[i - 1]) - types[i] = types[i - 1]; - } - - /* - W5. A sequence of European terminators adjacent to European numbers changes - to all European numbers: - */ - - for (var i = 0; i < strLength; ++i) { - if (types[i] == 'EN') { - // do before - for (var j = i - 1; j >= 0; --j) { - if (types[j] != 'ET') - break; - types[j] = 'EN'; - } - // do after - for (var j = i + 1; j < strLength; --j) { - if (types[j] != 'ET') - break; - types[j] = 'EN'; - } - } - } - - /* - W6. Otherwise, separators and terminators change to Other Neutral: - */ - - for (var i = 0; i < strLength; ++i) { - var t = types[i]; - if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') - types[i] = 'ON'; - } - - /* - W7. Search backwards from each instance of a European number until the - first strong type (R, L, or sor) is found. If an L is found, then change - the type of the European number to L. - */ - - var lastType = sor; - for (var i = 0; i < strLength; ++i) { - var t = types[i]; - if (t == 'EN') - types[i] = (lastType == 'L') ? 'L' : 'EN'; - else if (t == 'R' || t == 'L') - lastType = t; - } - - /* - N1. A sequence of neutrals takes the direction of the surrounding strong - text if the text on both sides has the same direction. European and Arabic - numbers are treated as though they were R. Start-of-level-run (sor) and - end-of-level-run (eor) are used at level run boundaries. - */ - - for (var i = 0; i < strLength; ++i) { - if (types[i] == 'ON') { - var end = findUnequal(types, i + 1, 'ON'); - var before = sor; - if (i > 0) - before = types[i - 1]; - var after = eor; - if (end + 1 < strLength) - after = types[end + 1]; - if (before != 'L') - before = 'R'; - if (after != 'L') - after = 'R'; - if (before == after) - setValues(types, i, end, before); - i = end - 1; // reset to end (-1 so next iteration is ok) - } - } - - /* - N2. Any remaining neutrals take the embedding direction. - */ - - for (var i = 0; i < strLength; ++i) { - if (types[i] == 'ON') - types[i] = e; - } - - /* - I1. For all characters with an even (left-to-right) embedding direction, - those of type R go up one level and those of type AN or EN go up two - levels. - I2. For all characters with an odd (right-to-left) embedding direction, - those of type L, EN or AN go up one level. - */ - - for (var i = 0; i < strLength; ++i) { - var t = types[i]; - if (isEven(levels[i])) { - if (t == 'R') { - levels[i] += 1; - } else if (t == 'AN' || t == 'EN') { - levels[i] += 2; - } - } else { // isOdd, so - if (t == 'L' || t == 'AN' || t == 'EN') { - levels[i] += 1; - } - } - } - - /* - L1. On each line, reset the embedding level of the following characters to - the paragraph embedding level: - - segment separators, - paragraph separators, - any sequence of whitespace characters preceding a segment separator or - paragraph separator, and any sequence of white space characters at the end - of the line. - */ - - // don't bother as text is only single line - - /* - L2. From the highest level found in the text to the lowest odd level on - each line, reverse any contiguous sequence of characters that are at that - level or higher. - */ - - // find highest level & lowest odd level - - var highestLevel = -1; - var lowestOddLevel = 99; - for (var i = 0, ii = levels.length; i < ii; ++i) { - var level = levels[i]; - if (highestLevel < level) - highestLevel = level; - if (lowestOddLevel > level && isOdd(level)) - lowestOddLevel = level; - } - - // now reverse between those limits - - for (var level = highestLevel; level >= lowestOddLevel; --level) { - // find segments to reverse - var start = -1; - for (var i = 0, ii = levels.length; i < ii; ++i) { - if (levels[i] < level) { - if (start >= 0) { - reverseValues(chars, start, i); - start = -1; - } - } else if (start < 0) { - start = i; - } - } - if (start >= 0) { - reverseValues(chars, start, levels.length); - } - } - - /* - L3. Combining marks applied to a right-to-left base character will at this - point precede their base character. If the rendering engine expects them to - follow the base characters in the final display process, then the ordering - of the marks and the base character must be reversed. - */ - - // don't bother for now - - /* - L4. A character that possesses the mirrored property as specified by - Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved - directionality of that character is R. - */ - - // don't mirror as characters are already mirrored in the pdf - - // Finally, return string - - var result = ''; - for (var i = 0, ii = chars.length; i < ii; ++i) { - var ch = chars[i]; - if (ch != '<' && ch != '>') - result += ch; - } - return result; - }); -})(); +/* -*- 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 bidi = PDFJS.bidi = (function bidiClosure() { + // Character types for symbols from 0000 to 00FF. + var baseTypes = [ + 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS', + 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', + 'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON', + 'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN', + 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON', + 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', + 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN', + 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', + 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', + 'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON', + 'ON', 'ON', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON', + 'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', + 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L' + ]; + + // Character types for symbols from 0600 to 06FF + var arabicTypes = [ + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', + 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', + 'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', + 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'ON', 'NSM', + 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL' + ]; + + function isOdd(i) { + return (i & 1) != 0; + } + + function isEven(i) { + return (i & 1) == 0; + } + + function findUnequal(arr, start, value) { + var j; + for (var j = start, jj = arr.length; j < jj; ++j) { + if (arr[j] != value) + return j; + } + return j; + } + + function setValues(arr, start, end, value) { + for (var j = start; j < end; ++j) { + arr[j] = value; + } + } + + function reverseValues(arr, start, end) { + for (var i = start, j = end - 1; i < j; ++i, --j) { + var temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + + function mirrorGlyphs(c) { + /* + # BidiMirroring-1.txt + 0028; 0029 # LEFT PARENTHESIS + 0029; 0028 # RIGHT PARENTHESIS + 003C; 003E # LESS-THAN SIGN + 003E; 003C # GREATER-THAN SIGN + 005B; 005D # LEFT SQUARE BRACKET + 005D; 005B # RIGHT SQUARE BRACKET + 007B; 007D # LEFT CURLY BRACKET + 007D; 007B # RIGHT CURLY BRACKET + 00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + */ + switch (c) { + case '(': + return ')'; + case ')': + return '('; + case '<': + return '>'; + case '>': + return '<'; + case ']': + return '['; + case '[': + return ']'; + case '}': + return '{'; + case '{': + return '}'; + case '\u00AB': + return '\u00BB'; + case '\u00BB': + return '\u00AB'; + default: + return c; + } + } + + function bidi(text, startLevel) { + var str = text.str; + var strLength = str.length; + if (strLength == 0) + return str; + + // get types, fill arrays + + var chars = new Array(strLength); + var types = new Array(strLength); + var oldtypes = new Array(strLength); + var numBidi = 0; + + for (var i = 0; i < strLength; ++i) { + chars[i] = str.charAt(i); + + var charCode = str.charCodeAt(i); + var charType = 'L'; + if (charCode <= 0x00ff) + charType = baseTypes[charCode]; + else if (0x0590 <= charCode && charCode <= 0x05f4) + charType = 'R'; + else if (0x0600 <= charCode && charCode <= 0x06ff) + charType = arabicTypes[charCode & 0xff]; + else if (0x0700 <= charCode && charCode <= 0x08AC) + charType = 'AL'; + + if (charType == 'R' || charType == 'AL' || charType == 'AN') + numBidi++; + + oldtypes[i] = types[i] = charType; + } + + // detect the bidi method + // if there are no rtl characters then no bidi needed + // if less than 30% chars are rtl then string is primarily ltr + // if more than 30% chars are rtl then string is primarily rtl + if (numBidi == 0) { + text.direction = 'ltr'; + return str; + } + + if (startLevel == -1) { + if ((strLength / numBidi) < 0.3) { + text.direction = 'ltr'; + startLevel = 0; + } else { + text.direction = 'rtl'; + startLevel = 1; + } + } + + var levels = new Array(strLength); + + for (var i = 0; i < strLength; ++i) { + levels[i] = startLevel; + } + + var diffChars = new Array(strLength); + var diffLevels = new Array(strLength); + var diffTypes = new Array(strLength); + + /* + X1-X10: skip most of this, since we are NOT doing the embeddings. + */ + + var e = isOdd(startLevel) ? 'R' : 'L'; + var sor = e; + var eor = sor; + + /* + W1. Examine each non-spacing mark (NSM) in the level run, and change the + type of the NSM to the type of the previous character. If the NSM is at the + start of the level run, it will get the type of sor. + */ + + var lastType = sor; + for (var i = 0; i < strLength; ++i) { + if (types[i] == 'NSM') + types[i] = lastType; + else + lastType = types[i]; + } + + /* + W2. Search backwards from each instance of a European number until the + first strong type (R, L, AL, or sor) is found. If an AL is found, change + the type of the European number to Arabic number. + */ + + var lastType = sor; + for (var i = 0; i < strLength; ++i) { + var t = types[i]; + if (t == 'EN') + types[i] = (lastType == 'AL') ? 'AN' : 'EN'; + else if (t == 'R' || t == 'L' || t == 'AL') + lastType = t; + } + + /* + W3. Change all ALs to R. + */ + + for (var i = 0; i < strLength; ++i) { + var t = types[i]; + if (t == 'AL') + types[i] = 'R'; + } + + /* + W4. A single European separator between two European numbers changes to a + European number. A single common separator between two numbers of the same + type changes to that type: + */ + + for (var i = 1; i < strLength - 1; ++i) { + if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN') + types[i] = 'EN'; + if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') && + types[i + 1] == types[i - 1]) + types[i] = types[i - 1]; + } + + /* + W5. A sequence of European terminators adjacent to European numbers changes + to all European numbers: + */ + + for (var i = 0; i < strLength; ++i) { + if (types[i] == 'EN') { + // do before + for (var j = i - 1; j >= 0; --j) { + if (types[j] != 'ET') + break; + types[j] = 'EN'; + } + // do after + for (var j = i + 1; j < strLength; --j) { + if (types[j] != 'ET') + break; + types[j] = 'EN'; + } + } + } + + /* + W6. Otherwise, separators and terminators change to Other Neutral: + */ + + for (var i = 0; i < strLength; ++i) { + var t = types[i]; + if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS') + types[i] = 'ON'; + } + + /* + W7. Search backwards from each instance of a European number until the + first strong type (R, L, or sor) is found. If an L is found, then change + the type of the European number to L. + */ + + var lastType = sor; + for (var i = 0; i < strLength; ++i) { + var t = types[i]; + if (t == 'EN') + types[i] = (lastType == 'L') ? 'L' : 'EN'; + else if (t == 'R' || t == 'L') + lastType = t; + } + + /* + N1. A sequence of neutrals takes the direction of the surrounding strong + text if the text on both sides has the same direction. European and Arabic + numbers are treated as though they were R. Start-of-level-run (sor) and + end-of-level-run (eor) are used at level run boundaries. + */ + + for (var i = 0; i < strLength; ++i) { + if (types[i] == 'ON') { + var end = findUnequal(types, i + 1, 'ON'); + var before = sor; + if (i > 0) + before = types[i - 1]; + var after = eor; + if (end + 1 < strLength) + after = types[end + 1]; + if (before != 'L') + before = 'R'; + if (after != 'L') + after = 'R'; + if (before == after) + setValues(types, i, end, before); + i = end - 1; // reset to end (-1 so next iteration is ok) + } + } + + /* + N2. Any remaining neutrals take the embedding direction. + */ + + for (var i = 0; i < strLength; ++i) { + if (types[i] == 'ON') + types[i] = e; + } + + /* + I1. For all characters with an even (left-to-right) embedding direction, + those of type R go up one level and those of type AN or EN go up two + levels. + I2. For all characters with an odd (right-to-left) embedding direction, + those of type L, EN or AN go up one level. + */ + + for (var i = 0; i < strLength; ++i) { + var t = types[i]; + if (isEven(levels[i])) { + if (t == 'R') { + levels[i] += 1; + } else if (t == 'AN' || t == 'EN') { + levels[i] += 2; + } + } else { // isOdd, so + if (t == 'L' || t == 'AN' || t == 'EN') { + levels[i] += 1; + } + } + } + + /* + L1. On each line, reset the embedding level of the following characters to + the paragraph embedding level: + + segment separators, + paragraph separators, + any sequence of whitespace characters preceding a segment separator or + paragraph separator, and any sequence of white space characters at the end + of the line. + */ + + // don't bother as text is only single line + + /* + L2. From the highest level found in the text to the lowest odd level on + each line, reverse any contiguous sequence of characters that are at that + level or higher. + */ + + // find highest level & lowest odd level + + var highestLevel = -1; + var lowestOddLevel = 99; + for (var i = 0, ii = levels.length; i < ii; ++i) { + var level = levels[i]; + if (highestLevel < level) + highestLevel = level; + if (lowestOddLevel > level && isOdd(level)) + lowestOddLevel = level; + } + + // now reverse between those limits + + for (var level = highestLevel; level >= lowestOddLevel; --level) { + // find segments to reverse + var start = -1; + for (var i = 0, ii = levels.length; i < ii; ++i) { + if (levels[i] < level) { + if (start >= 0) { + reverseValues(chars, start, i); + start = -1; + } + } else if (start < 0) { + start = i; + } + } + if (start >= 0) { + reverseValues(chars, start, levels.length); + } + } + + /* + L3. Combining marks applied to a right-to-left base character will at this + point precede their base character. If the rendering engine expects them to + follow the base characters in the final display process, then the ordering + of the marks and the base character must be reversed. + */ + + // don't bother for now + + /* + L4. A character that possesses the mirrored property as specified by + Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved + directionality of that character is R. + */ + + // don't mirror as characters are already mirrored in the pdf + + // Finally, return string + + var result = ''; + for (var i = 0, ii = chars.length; i < ii; ++i) { + var ch = chars[i]; + if (ch != '<' && ch != '>') + result += ch; + } + return result; + } + + return bidi; +})(); + diff --git a/src/canvas.js b/src/canvas.js index 54ab5b4d0..7bf94a642 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -70,7 +70,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { return CanvasExtraState; })(); -function ScratchCanvas(width, height) { +function createScratchCanvas(width, height) { var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; @@ -188,9 +188,9 @@ function addContextCurrentTransform(ctx) { } var CanvasGraphics = (function CanvasGraphicsClosure() { - // Defines the time the executeIRQueue is going to be executing + // Defines the time the executeOperatorList is going to be executing // before it stops and shedules a continue of execution. - var kExecutionTime = 50; + var kExecutionTime = 15; function CanvasGraphics(canvasCtx, objs, textLayer) { this.ctx = canvasCtx; @@ -199,7 +199,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.pendingClip = null; this.res = null; this.xobjs = null; - this.ScratchCanvas = ScratchCanvas; this.objs = objs; this.textLayer = textLayer; if (canvasCtx) { @@ -229,7 +228,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 'setStrokeColor': true, 'setStrokeColorN': true, 'setFillColor': true, - 'setFillColorN_IR': true, + 'setFillColorN': true, 'setStrokeGray': true, 'setFillGray': true, 'setStrokeRGBColor': true, @@ -268,15 +267,16 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.textLayer.beginLayout(); }, - executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, - executionStartIdx, continueCallback, - stepper) { - var argsArray = codeIR.argsArray; - var fnArray = codeIR.fnArray; + executeOperatorList: function canvasGraphicsExecuteOperatorList( + operatorList, + executionStartIdx, continueCallback, + stepper) { + var argsArray = operatorList.argsArray; + var fnArray = operatorList.fnArray; var i = executionStartIdx || 0; var argsArrayLen = argsArray.length; - // Sometimes the IRQueue to execute is empty. + // Sometimes the OperatorList to execute is empty. if (argsArrayLen == i) { return i; } @@ -314,7 +314,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { i++; - // If the entire IRQueue was executed, stop as were done. + // If the entire operatorList was executed, stop as were done. if (i == argsArrayLen) { return i; } @@ -327,8 +327,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return i; } - // If the IRQueue isn't executed completly yet OR the execution time - // was short enough, do another execution round. + // If the operatorList isn't executed completely yet OR the execution + // time was short enough, do another execution round. } }, @@ -556,7 +556,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.current.leading = -leading; }, setFont: function canvasGraphicsSetFont(fontRefName, size) { - var fontObj = this.objs.get(fontRefName).fontObj; + var fontObj = this.objs.get(fontRefName); var current = this.current; if (!fontObj) @@ -707,7 +707,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.save(); ctx.scale(fontSize, fontSize); ctx.transform.apply(ctx, fontMatrix); - this.executeIRQueue(glyph.codeIRQueue); + this.executeOperatorList(glyph.operatorList); this.restore(); var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); @@ -908,7 +908,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.strokeStyle = color; this.current.strokeColor = color; }, - getColorN_IR_Pattern: function canvasGraphicsGetColorN_IR_Pattern(IR, cs) { + getColorN_Pattern: function canvasGraphicsGetColorN_Pattern(IR, cs) { if (IR[0] == 'TilingPattern') { var args = IR[1]; var base = cs.base; @@ -930,11 +930,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } return pattern; }, - setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) { + setStrokeColorN: function canvasGraphicsSetStrokeColorN(/*...*/) { var cs = this.current.strokeColorSpace; if (cs.name == 'Pattern') { - this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs); + this.current.strokeColor = this.getColorN_Pattern(arguments, cs); } else { this.setStrokeColor.apply(this, arguments); } @@ -946,11 +946,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.fillStyle = color; this.current.fillColor = color; }, - setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) { + setFillColorN: function canvasGraphicsSetFillColorN(/*...*/) { var cs = this.current.fillColorSpace; if (cs.name == 'Pattern') { - this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs); + this.current.fillColor = this.getColorN_Pattern(arguments, cs); } else { this.setFillColor.apply(this, arguments); } @@ -1116,7 +1116,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // scale the image to the unit square ctx.scale(1 / w, -1 / h); - var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCanvas = createScratchCanvas(w, h); var tmpCtx = tmpCanvas.getContext('2d'); var fillColor = this.current.fillColor; @@ -1147,7 +1147,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // scale the image to the unit square ctx.scale(1 / w, -1 / h); - var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCanvas = createScratchCanvas(w, h); var tmpCtx = tmpCanvas.getContext('2d'); this.putBinaryImageData(tmpCtx, imgData, w, h); diff --git a/src/colorspace.js b/src/colorspace.js index 57bc9c846..d3d392361 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -135,6 +135,7 @@ var ColorSpace = (function ColorSpaceClosure() { basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); return ['PatternCS', basePatternCS]; case 'Indexed': + case 'I': var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); var hiVal = cs[2] + 1; var lookup = xref.fetchIfRef(cs[3]); diff --git a/src/core.js b/src/core.js index 86e3eeb5f..ecc2c94a5 100644 --- a/src/core.js +++ b/src/core.js @@ -63,13 +63,8 @@ var Page = (function PageClosure() { function Page(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; - this.stats = { - create: Date.now(), - compile: 0.0, - fonts: 0.0, - images: 0.0, - render: 0.0 - }; + this.stats = new StatTimer(); + this.stats.enabled = !!globalScope.PDFJS.enableStats; this.xref = xref; this.ref = ref; @@ -175,10 +170,10 @@ var Page = (function PageClosure() { return shadow(this, 'rotate', rotate); }, - startRenderingFromIRQueue: function pageStartRenderingFromIRQueue( - IRQueue, fonts) { + startRenderingFromOperatorList: function pageStartRenderingFromOperatorList( + operatorList, fonts) { var self = this; - this.IRQueue = IRQueue; + this.operatorList = operatorList; var displayContinuation = function pageDisplayContinuation() { // Always defer call to display() to work around bug in @@ -189,17 +184,20 @@ var Page = (function PageClosure() { }; this.ensureFonts(fonts, - function pageStartRenderingFromIRQueueEnsureFonts() { - displayContinuation(); - }); + function pageStartRenderingFromOperatorListEnsureFonts() { + displayContinuation(); + } + ); }, - getIRQueue: function pageGetIRQueue(handler, dependency) { - if (this.IRQueue) { + getOperatorList: function pageGetOperatorList(handler, dependency) { + if (this.operatorList) { // content was compiled - return this.IRQueue; + return this.operatorList; } + this.stats.time('Build IR Queue'); + var xref = this.xref; var content = xref.fetchIfRef(this.content); var resources = xref.fetchIfRef(this.resources); @@ -216,30 +214,33 @@ var Page = (function PageClosure() { var pe = this.pe = new PartialEvaluator( xref, handler, 'p' + this.pageNumber + '_'); - var IRQueue = {}; - return (this.IRQueue = pe.getIRQueue(content, resources, IRQueue, - dependency)); + + this.operatorList = pe.getOperatorList(content, resources, dependency); + this.stats.timeEnd('Build IR Queue'); + return this.operatorList; }, ensureFonts: function pageEnsureFonts(fonts, callback) { + this.stats.time('Font Loading'); // Convert the font names to the corresponding font obj. for (var i = 0, ii = fonts.length; i < ii; i++) { fonts[i] = this.objs.objs[fonts[i]].data; } // Load all the fonts - var fontObjs = FontLoader.bind( + FontLoader.bind( fonts, function pageEnsureFontsFontObjs(fontObjs) { - this.stats.fonts = Date.now(); + this.stats.timeEnd('Font Loading'); callback.call(this); - }.bind(this), - this.objs + }.bind(this) ); }, display: function pageDisplay(gfx, callback) { + var stats = this.stats; + stats.time('Rendering'); var xref = this.xref; var resources = xref.fetchIfRef(this.resources); var mediaBox = xref.fetchIfRef(this.mediaBox); @@ -253,21 +254,23 @@ var Page = (function PageClosure() { rotate: this.rotate }); var startIdx = 0; - var length = this.IRQueue.fnArray.length; - var IRQueue = this.IRQueue; + var length = this.operatorList.fnArray.length; + var operatorList = this.operatorList; var stepper = null; if (PDFJS.pdfBug && StepperManager.enabled) { stepper = StepperManager.create(this.pageNumber); - stepper.init(IRQueue); + stepper.init(operatorList); stepper.nextBreakPoint = stepper.getNextBreakPoint(); } var self = this; function next() { - startIdx = gfx.executeIRQueue(IRQueue, startIdx, next, stepper); + startIdx = + gfx.executeOperatorList(operatorList, startIdx, next, stepper); if (startIdx == length) { - self.stats.render = Date.now(); gfx.endDrawing(); + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); if (callback) callback(); } } @@ -310,6 +313,22 @@ var Page = (function PageClosure() { return null; return item.get(name); } + function isValidUrl(url) { + if (!url) + return false; + var colon = url.indexOf(':'); + if (colon < 0) + return false; + var protocol = url.substr(0, colon); + switch (protocol) { + case 'http': + case 'https': + case 'ftp': + return true; + default: + return false; + } + } var annotations = xref.fetchIfRef(this.annotations) || []; var i, n = annotations.length; @@ -338,7 +357,12 @@ var Page = (function PageClosure() { if (a) { switch (a.get('S').name) { case 'URI': - item.url = a.get('URI'); + var url = a.get('URI'); + // TODO: pdf spec mentions urls can be relative to a Base + // entry in the dictionary. + if (!isValidUrl(url)) + url = ''; + item.url = url; break; case 'GoTo': item.dest = a.get('D'); @@ -410,16 +434,16 @@ var Page = (function PageClosure() { return items; }, startRendering: function pageStartRendering(ctx, callback, textLayer) { - this.startRenderingTime = Date.now(); - - // If there is no displayReadyPromise yet, then the IRQueue was never + var stats = this.stats; + stats.time('Overall'); + // If there is no displayReadyPromise yet, then the operatorList was never // requested before. Make the request and create the promise. if (!this.displayReadyPromise) { this.pdf.startRendering(this); this.displayReadyPromise = new Promise(); } - // Once the IRQueue and fonts are loaded, perform the actual rendering. + // Once the operatorList and fonts are loaded, do the actual rendering. this.displayReadyPromise.then( function pageDisplayReadyPromise() { var gfx = new CanvasGraphics(ctx, this.objs, textLayer); @@ -451,9 +475,6 @@ var Page = (function PageClosure() { * Right now there exists one PDFDocModel on the main thread + one object * for each worker. If there is no worker support enabled, there are two * `PDFDocModel` objects on the main thread created. - * TODO: Refactor the internal object structure, such that there is no - * need for the `PDFDocModel` anymore and there is only one object on the - * main thread and not one entire copy on each worker instance. */ var PDFDocModel = (function PDFDocModelClosure() { function PDFDocModel(arg, callback) { @@ -622,9 +643,9 @@ var PDFDoc = (function PDFDocClosure() { this.data = data; this.stream = stream; - this.pdf = new PDFDocModel(stream); - this.fingerprint = this.pdf.getFingerprint(); - this.catalog = this.pdf.catalog; + this.pdfModel = new PDFDocModel(stream); + this.fingerprint = this.pdfModel.getFingerprint(); + this.catalog = this.pdfModel.catalog; this.objs = new PDFObjects(); this.pageCache = []; @@ -711,7 +732,8 @@ var PDFDoc = (function PDFDocClosure() { var page = this.pageCache[pageNum]; var depFonts = data.depFonts; - page.startRenderingFromIRQueue(data.IRQueue, depFonts); + page.stats.timeEnd('Page Request'); + page.startRenderingFromOperatorList(data.operatorList, depFonts); }, this); messageHandler.on('obj', function pdfDocObj(data) { @@ -738,31 +760,16 @@ var PDFDoc = (function PDFDocClosure() { file = new Stream(file, 0, file.length, fontFileDict); } - // For now, resolve the font object here direclty. The real font - // object is then created in FontLoader.bind(). - this.objs.resolve(id, { - name: name, - file: file, - properties: properties - }); + // At this point, only the font object is created but the font is + // not yet attached to the DOM. This is done in `FontLoader.bind`. + var font = new Font(name, file, properties); + this.objs.resolve(id, font); break; default: error('Got unkown object type ' + type); } }, this); - messageHandler.on('font_ready', function pdfDocFontReady(data) { - var id = data[0]; - var font = new FontShape(data[1]); - - // If there is no string, then there is nothing to attach to the DOM. - if (!font.str) { - this.objs.resolve(id, font); - } else { - this.objs.setData(id, font); - } - }.bind(this)); - messageHandler.on('page_error', function pdfDocError(data) { var page = this.pageCache[data.pageNum]; if (page.displayReadyPromise) @@ -784,7 +791,7 @@ var PDFDoc = (function PDFDocClosure() { var size = width * height; var rgbaLength = size * 4; var buf = new Uint8Array(size * components); - var tmpCanvas = new ScratchCanvas(width, height); + var tmpCanvas = createScratchCanvas(width, height); var tmpCtx = tmpCanvas.getContext('2d'); tmpCtx.drawImage(img, 0, 0); var data = tmpCtx.getImageData(0, 0, width, height).data; @@ -813,12 +820,13 @@ var PDFDoc = (function PDFDocClosure() { }, get numPages() { - return this.pdf.numPages; + return this.pdfModel.numPages; }, startRendering: function pdfDocStartRendering(page) { // The worker might not be ready to receive the page request yet. this.workerReadyPromise.then(function pdfDocStartRenderingThen() { + page.stats.time('Page Request'); this.messageHandler.send('page_request', page.pageNumber + 1); }.bind(this)); }, @@ -827,7 +835,7 @@ var PDFDoc = (function PDFDocClosure() { if (this.pageCache[n]) return this.pageCache[n]; - var page = this.pdf.getPage(n); + var page = this.pdfModel.getPage(n); // Add a reference to the objects such that Page can forward the reference // to the CanvasGraphics and so on. page.objs = this.objs; diff --git a/src/crypto.js b/src/crypto.js index 7c34a8506..b1a298223 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -570,7 +570,6 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { }; } error('Unknown crypto method'); - return null; } CipherTransformFactory.prototype = { diff --git a/src/evaluator.js b/src/evaluator.js index e34787e41..1d83c380b 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -112,8 +112,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }; PartialEvaluator.prototype = { - getIRQueue: function partialEvaluatorGetIRQueue(stream, resources, - queue, dependency) { + getOperatorList: function partialEvaluatorGetOperatorList(stream, resources, + dependency, queue) { var self = this; var xref = this.xref; @@ -136,8 +136,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var fontRes = resources.get('Font'); - // TODO: TOASK: Is it possible to get here? If so, what does - // args[0].name should be like??? assert(fontRes, 'fontRes not available'); fontRes = xref.fetchIfRef(fontRes); @@ -177,7 +175,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // Ensure the font is ready before the font is set // and later on used for drawing. - // TODO: This should get insert to the IRQueue only once per + // OPTIMIZE: This should get insert to the operatorList only once per // page. insertDependency([loadedName]); return loadedName; @@ -239,6 +237,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, handler, xref, resources, image, inline); } + if (!queue) + queue = {}; + if (!queue.argsArray) { queue.argsArray = []; } @@ -280,9 +281,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // TODO figure out how to type-check vararg functions if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { - // Use the IR version for setStroke/FillColorN. - fn += '_IR'; - // compile tiling patterns var patternName = args[args.length - 1]; // SCN/scn applies patterns along with normal colors @@ -295,15 +293,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (typeNum == TILING_PATTERN) { // Create an IR of the pattern code. var depIdx = dependencyArray.length; - var queueObj = {}; - var codeIR = this.getIRQueue(pattern, dict.get('Resources') || - resources, queueObj, dependencyArray); + var operatorList = this.getOperatorList(pattern, + dict.get('Resources') || resources, dependencyArray); // Add the dependencies that are required to execute the - // codeIR. + // operatorList. insertDependency(dependencyArray.slice(depIdx)); - args = TilingPattern.getIR(codeIR, dict, args); + args = TilingPattern.getIR(operatorList, dict, args); } else if (typeNum == SHADING_PATTERN) { var shading = xref.fetchIfRef(dict.get('Shading')); @@ -337,14 +334,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { fnArray.push('paintFormXObjectBegin'); argsArray.push([matrix, bbox]); - // This adds the IRQueue of the xObj to the current queue. + // This adds the operatorList of the xObj to the current queue. var depIdx = dependencyArray.length; - this.getIRQueue(xobj, xobj.dict.get('Resources') || resources, - queue, dependencyArray); + // Pass in the current `queue` object. That means the `fnArray` + // and the `argsArray` in this scope is reused and new commands + // are added to them. + this.getOperatorList(xobj, + xobj.dict.get('Resources') || resources, + dependencyArray, queue); // Add the dependencies that are required to execute the - // codeIR. + // operatorList. insertDependency(dependencyArray.slice(depIdx)); fn = 'paintFormXObjectEnd'; @@ -454,10 +455,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } - return { - fnArray: fnArray, - argsArray: argsArray - }; + return queue; }, extractDataStructures: function @@ -855,12 +853,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var charProcs = xref.fetchIfRef(dict.get('CharProcs')); var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; properties.resources = fontResources; - properties.charProcIRQueues = {}; + properties.charProcOperatorList = {}; for (var key in charProcs.map) { var glyphStream = xref.fetchIfRef(charProcs.map[key]); - var queueObj = {}; - properties.charProcIRQueues[key] = - this.getIRQueue(glyphStream, fontResources, queueObj, dependency); + properties.charProcOperatorList[key] = + this.getOperatorList(glyphStream, fontResources, dependency); } } diff --git a/src/fonts.js b/src/fonts.js index 28a95acbb..8473196a0 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -409,8 +409,8 @@ var FontLoader = { bind: function fontLoaderBind(fonts, callback) { function checkFontsLoaded() { - for (var i = 0, ii = objs.length; i < ii; i++) { - var fontObj = objs[i]; + for (var i = 0, ii = fonts.length; i < ii; i++) { + var fontObj = fonts[i]; if (fontObj.loading) { return false; } @@ -423,52 +423,45 @@ var FontLoader = { return true; } - var rules = [], names = [], objs = []; + var rules = [], names = [], fontsToLoad = []; + var fontCreateTimer = 0; for (var i = 0, ii = fonts.length; i < ii; i++) { var font = fonts[i]; - // If there is already a fontObj on the font, then it was loaded/attached - // to the page already and we don't have to do anything for this font - // here future. - if (font.fontObj) { + // Add the font to the DOM only once or skip if the font + // is already loaded. + if (font.attached || font.loading == false) { continue; } + font.attached = true; - var obj = new Font(font.name, font.file, font.properties); - - // Store the fontObj on the font such that `setFont` in CanvasGraphics - // can reuse it later again. - font.fontObj = obj; - - objs.push(obj); + fontsToLoad.push(font); var str = ''; - var data = obj.data; + var data = font.data; if (data) { var length = data.length; for (var j = 0; j < length; j++) str += String.fromCharCode(data[j]); - var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str); + var rule = font.bindDOM(str); if (rule) { rules.push(rule); - names.push(obj.loadedName); + names.push(font.loadedName); } } } this.listeningForFontLoad = false; if (!isWorker && rules.length) { - FontLoader.prepareFontLoadEvent(rules, names, objs); + FontLoader.prepareFontLoadEvent(rules, names, fontsToLoad); } if (!checkFontsLoaded()) { document.documentElement.addEventListener( 'pdfjsFontLoad', checkFontsLoaded, false); } - - return objs; }, // Set things up so that at least one pdfjsFontLoad event is // dispatched when all the @font-face |rules| for |names| have been @@ -476,7 +469,7 @@ var FontLoader = { // has already started in this (outer) document, so that they should // be ordered before the load in the subdocument. prepareFontLoadEvent: function fontLoaderPrepareFontLoadEvent(rules, names, - objs) { + fonts) { /** Hack begin */ // There's no event when a font has finished downloading so the // following code is a dirty hack to 'guess' when a font is @@ -500,6 +493,15 @@ var FontLoader = { // The postMessage() hackery was added to work around chrome bug // 82402. + // Validate the names parameter -- the values can used to construct HTML. + if (!/^\w+$/.test(names.join(''))) { + error('Invalid font name(s): ' + names.join()); + + // Normally the error-function throws. But if a malicious code + // intercepts the function call then the return is needed. + return; + } + var div = document.createElement('div'); div.setAttribute('style', 'visibility: hidden;' + @@ -517,8 +519,8 @@ var FontLoader = { 'message', function fontLoaderMessage(e) { var fontNames = JSON.parse(e.data); - for (var i = 0, ii = objs.length; i < ii; ++i) { - var font = objs[i]; + for (var i = 0, ii = fonts.length; i < ii; ++i) { + var font = fonts[i]; font.loading = false; } var evt = document.createEvent('Events'); @@ -764,7 +766,7 @@ var Font = (function FontClosure() { function Font(name, file, properties) { this.name = name; this.coded = properties.coded; - this.charProcIRQueues = properties.charProcIRQueues; + this.charProcOperatorList = properties.charProcOperatorList; this.resources = properties.resources; this.sizes = []; @@ -829,8 +831,6 @@ var Font = (function FontClosure() { return; } - this.loadedName = getUniqueName(); - properties.id = this.loadedName; var data; switch (type) { case 'Type1': @@ -864,6 +864,7 @@ var Font = (function FontClosure() { this.widthMultiplier = !properties.fontMatrix ? 1.0 : 1.0 / properties.fontMatrix[0]; this.encoding = properties.baseEncoding; + this.loadedName = properties.loadedName; this.loading = true; }; @@ -2273,17 +2274,6 @@ var Font = (function FontClosure() { } }, - bindWorker: function font_bindWorker(data) { - postMessage({ - action: 'font', - data: { - raw: data, - fontName: this.loadedName, - mimetype: this.mimetype - } - }); - }, - bindDOM: function font_bindDom(data) { var fontName = this.loadedName; @@ -2337,7 +2327,7 @@ var Font = (function FontClosure() { }, charToGlyph: function fonts_charToGlyph(charcode) { - var fontCharCode, width, codeIRQueue; + var fontCharCode, width, operatorList; var width = this.widths[charcode]; @@ -2372,7 +2362,7 @@ var Font = (function FontClosure() { break; case 'Type3': var glyphName = this.differences[charcode] || this.encoding[charcode]; - codeIRQueue = this.charProcIRQueues[glyphName]; + operatorList = this.charProcOperatorList[glyphName]; fontCharCode = charcode; break; case 'TrueType': @@ -2415,7 +2405,7 @@ var Font = (function FontClosure() { fontChar: String.fromCharCode(fontCharCode), unicode: unicodeChars, width: width, - codeIRQueue: codeIRQueue + operatorList: operatorList }; }, diff --git a/src/obj.js b/src/obj.js index 3cdee8778..3c649fb06 100644 --- a/src/obj.js +++ b/src/obj.js @@ -134,6 +134,8 @@ var Catalog = (function CatalogClosure() { while (queue.length > 0) { var i = queue.shift(); var outlineDict = xref.fetch(i.obj); + if (outlineDict === null) + continue; if (!outlineDict.has('Title')) error('Invalid outline item'); var dest = outlineDict.get('A'); @@ -512,7 +514,6 @@ var XRef = (function XRefClosure() { return dict; // nothing helps error('Invalid PDF structure'); - return null; }, readXRef: function readXref(startXRef) { var stream = this.stream; @@ -569,6 +570,8 @@ var XRef = (function XRefClosure() { }, getEntry: function xRefGetEntry(i) { var e = this.entries[i]; + if (e === null) + return null; return e.free ? null : e; // returns null is the entry is free }, fetchIfRef: function xRefFetchIfRef(obj) { @@ -719,12 +722,10 @@ var PDFObjects = (function PDFObjectsClosure() { // If there isn't an object yet or the object isn't resolved, then the // data isn't ready yet! - if (!obj || !obj.isResolved) { + if (!obj || !obj.isResolved) error('Requesting object that isn\'t resolved yet ' + objId); - return null; - } else { - return obj.data; - } + + return obj.data; }, /** diff --git a/src/parser.js b/src/parser.js index fc8f5bc66..fad8b2c03 100644 --- a/src/parser.js +++ b/src/parser.js @@ -53,15 +53,14 @@ var Parser = (function ParserClosure() { this.shift(); var dict = new Dict(); while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { + if (!isName(this.buf1)) error('Dictionary key must be a name object'); - } else { - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } + + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); } if (isEOF(this.buf1)) error('End of file inside dictionary'); @@ -106,15 +105,14 @@ var Parser = (function ParserClosure() { // parse dictionary var dict = new Dict(); while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { + if (!isName(this.buf1)) error('Dictionary key must be a name object'); - } else { - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } + + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); } // parse image stream @@ -176,10 +174,8 @@ var Parser = (function ParserClosure() { // get length var length = this.fetchIfRef(dict.get('Length')); - if (!isInt(length)) { + if (!isInt(length)) error('Bad ' + length + ' attribute in stream'); - length = 0; - } // skip over the stream data stream.pos = pos + length; @@ -208,14 +204,13 @@ var Parser = (function ParserClosure() { filter = filterArray[i]; if (!isName(filter)) error('Bad filter name: ' + filter); - else { - params = null; - if (isArray(paramsArray) && (i in paramsArray)) - params = paramsArray[i]; - stream = this.makeFilter(stream, filter.name, length, params); - // after the first stream the length variable is invalid - length = null; - } + + params = null; + if (isArray(paramsArray) && (i in paramsArray)) + params = paramsArray[i]; + stream = this.makeFilter(stream, filter.name, length, params); + // after the first stream the length variable is invalid + length = null; } } return stream; @@ -527,17 +522,15 @@ var Lexer = (function LexerClosure() { // fall through case ')': error('Illegal character: ' + ch); - return Error; } // command var str = ch; while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { stream.skip(); - if (str.length == 128) { + if (str.length == 128) error('Command token too long: ' + str.length); - break; - } + str += ch; } if (str == 'true') @@ -594,7 +587,6 @@ var Linearization = (function LinearizationClosure() { return obj; } error('"' + name + '" field in linearization table is invalid'); - return 0; }, getHint: function linearizationGetHint(index) { var linDict = this.linDict; @@ -607,7 +599,6 @@ var Linearization = (function LinearizationClosure() { return obj2; } error('Hints table in linearization table is invalid: ' + index); - return 0; }, get length() { if (!isDict(this.linDict)) diff --git a/src/pattern.js b/src/pattern.js index dff2a5b44..80ba159e7 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -82,7 +82,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() { fnObj = xref.fetchIfRef(fnObj); if (isArray(fnObj)) error('No support for array of functions'); - else if (!isPDFFunction(fnObj)) + if (!isPDFFunction(fnObj)) error('Invalid function'); var fn = PDFFunction.parse(xref, fnObj); @@ -190,7 +190,7 @@ var TilingPattern = (function TilingPatternClosure() { var MAX_PATTERN_SIZE = 512; function TilingPattern(IR, color, ctx, objs) { - var IRQueue = IR[2]; + var operatorList = IR[2]; this.matrix = IR[3]; var bbox = IR[4]; var xstep = IR[5]; @@ -222,7 +222,7 @@ var TilingPattern = (function TilingPatternClosure() { width = height = MAX_PATTERN_SIZE; } - var tmpCanvas = new ScratchCanvas(width, height); + var tmpCanvas = createScratchCanvas(width, height); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext('2d'); @@ -259,12 +259,12 @@ var TilingPattern = (function TilingPatternClosure() { graphics.endPath(); } - graphics.executeIRQueue(IRQueue); + graphics.executeOperatorList(operatorList); this.canvas = tmpCanvas; } - TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) { + TilingPattern.getIR = function tiling_getIR(operatorList, dict, args) { var matrix = dict.get('Matrix'); var bbox = dict.get('BBox'); var xstep = dict.get('XStep'); @@ -272,7 +272,7 @@ var TilingPattern = (function TilingPatternClosure() { var paintType = dict.get('PaintType'); return [ - 'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType + 'TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, paintType ]; }; diff --git a/src/util.js b/src/util.js index 93bd36b55..243d66262 100644 --- a/src/util.js +++ b/src/util.js @@ -402,7 +402,7 @@ var Promise = (function PromiseClosure() { if (this.isResolved) { var data = this.data; callback.call(null, data); - } else if (this.isRejected && errorback) { + } else if (this.isRejected && errback) { var error = this.error; errback.call(null, error); } else { @@ -416,3 +416,55 @@ var Promise = (function PromiseClosure() { return Promise; })(); +var StatTimer = (function StatTimerClosure() { + function rpad(str, pad, length) { + while (str.length < length) + str += pad; + return str; + } + function StatTimer() { + this.started = {}; + this.times = []; + this.enabled = true; + } + StatTimer.prototype = { + time: function statTimerTime(name) { + if (!this.enabled) + return; + if (name in this.started) + throw 'Timer is already running for ' + name; + this.started[name] = Date.now(); + }, + timeEnd: function statTimerTimeEnd(name) { + if (!this.enabled) + return; + if (!(name in this.started)) + throw 'Timer has not been started for ' + name; + this.times.push({ + 'name': name, + 'start': this.started[name], + 'end': Date.now() + }); + // Remove timer from started so it can be called again. + delete this.started[name]; + }, + toString: function statTimerToString() { + var times = this.times; + var out = ''; + // Find the longest name for padding purposes. + var longest = 0; + for (var i = 0, ii = times.length; i < ii; ++i) { + var name = times[i]['name']; + if (name.length > longest) + longest = name.length; + } + for (var i = 0, ii = times.length; i < ii; ++i) { + var span = times[i]; + var duration = span.end - span.start; + out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; + } + return out; + } + }; + return StatTimer; +})(); diff --git a/src/worker.js b/src/worker.js index 3122d741e..42bd61050 100644 --- a/src/worker.js +++ b/src/worker.js @@ -79,7 +79,7 @@ MessageHandler.prototype = { var WorkerMessageHandler = { setup: function wphSetup(handler) { - var pdfDoc = null; + var pdfModel = null; handler.on('test', function wphSetupTest(data) { handler.send('test', data instanceof Uint8Array); @@ -88,7 +88,7 @@ var WorkerMessageHandler = { handler.on('doc', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. - pdfDoc = new PDFDocModel(new Stream(data)); + pdfModel = new PDFDocModel(new Stream(data)); }); handler.on('page_request', function wphSetupPageRequest(pageNum) { @@ -103,14 +103,14 @@ var WorkerMessageHandler = { var start = Date.now(); var dependency = []; - var IRQueue = null; + var operatorList = null; try { - var page = pdfDoc.getPage(pageNum); + var page = pdfModel.getPage(pageNum); // Pre compile the pdf page and fetch the fonts/images. - IRQueue = page.getIRQueue(handler, dependency); + operatorList = page.getOperatorList(handler, dependency); } catch (e) { var minimumStackMessage = - 'worker.js: while trying to getPage() and getIRQueue()'; + 'worker.js: while trying to getPage() and getOperatorList()'; // Turn the error into an obj that can be serialized if (typeof e === 'string') { @@ -137,8 +137,8 @@ var WorkerMessageHandler = { return; } - console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum, - Date.now() - start, IRQueue.fnArray.length); + console.log('page=%d - getOperatorList: time=%dms, len=%d', pageNum, + Date.now() - start, operatorList.fnArray.length); // Filter the dependecies for fonts. var fonts = {}; @@ -151,59 +151,10 @@ var WorkerMessageHandler = { handler.send('page', { pageNum: pageNum, - IRQueue: IRQueue, + operatorList: operatorList, depFonts: Object.keys(fonts) }); }, this); - - handler.on('font', function wphSetupFont(data) { - var objId = data[0]; - var name = data[1]; - var file = data[2]; - var properties = data[3]; - - var font = { - name: name, - file: file, - properties: properties - }; - - // Some fonts don't have a file, e.g. the build in ones like Arial. - if (file) { - var fontFileDict = new Dict(); - fontFileDict.map = file.dict.map; - - var fontFile = new Stream(file.bytes, file.start, - file.end - file.start, fontFileDict); - - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - font.file = new FlateStream(fontFile); - } else { - font.file = fontFile; - } - } - - var obj = new Font(font.name, font.file, font.properties); - - var str = ''; - var objData = obj.data; - if (objData) { - var length = objData.length; - for (var j = 0; j < length; ++j) - str += String.fromCharCode(objData[j]); - } - - obj.str = str; - - // Remove the data array form the font object, as it's not needed - // anymore as we sent over the ready str. - delete obj.data; - - handler.send('font_ready', [objId, obj]); - }); } }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 434ef2436..ef1f6835a 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -20,6 +20,7 @@ !scan-bad.pdf !freeculture.pdf !pdfkit_compressed.pdf +!TAMReview.pdf !issue918.pdf !issue1249.pdf !smaskdim.pdf diff --git a/test/pdfs/TAMReview.pdf b/test/pdfs/TAMReview.pdf new file mode 100644 index 000000000..dc77653c5 Binary files /dev/null and b/test/pdfs/TAMReview.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 9f25a39b4..c3098da81 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -472,6 +472,14 @@ "rounds": 1, "type": "eq" }, + { "id": "tamreview", + "file": "pdfs/TAMReview.pdf", + "md5": "8039aba56790d3597d2bc8c794a51301", + "rounds": 1, + "pageLimit": 5, + "link": true, + "type": "eq" + }, { "id": "issue925", "file": "pdfs/issue925.pdf", "md5": "f58fe943090aff89dcc8e771bc0db4c2", diff --git a/web/debugger.js b/web/debugger.js index 43407fdaa..00f5f6fd4 100644 --- a/web/debugger.js +++ b/web/debugger.js @@ -318,6 +318,58 @@ var Stepper = (function StepperClosure() { return Stepper; })(); +var Stats = (function Stats() { + var stats = []; + function clear(node) { + while (node.hasChildNodes()) + node.removeChild(node.lastChild); + } + function getStatIndex(pageNumber) { + for (var i = 0, ii = stats.length; i < ii; ++i) + if (stats[i].pageNumber === pageNumber) + return i; + return false; + } + return { + // Poperties/functions needed by PDFBug. + id: 'Stats', + name: 'Stats', + panel: null, + manager: null, + init: function init() { + this.panel.setAttribute('style', 'padding: 5px;'); + PDFJS.enableStats = true; + }, + enabled: false, + active: false, + // Stats specific functions. + add: function(pageNumber, stat) { + if (!stat) + return; + var statsIndex = getStatIndex(pageNumber); + if (statsIndex !== false) { + var b = stats[statsIndex]; + this.panel.removeChild(b.div); + stats.splice(statsIndex, 1); + } + var wrapper = document.createElement('div'); + wrapper.className = 'stats'; + var title = document.createElement('div'); + title.className = 'title'; + title.textContent = 'Page: ' + pageNumber; + var statsDiv = document.createElement('div'); + statsDiv.textContent = stat.toString(); + wrapper.appendChild(title); + wrapper.appendChild(statsDiv); + stats.push({ pageNumber: pageNumber, div: wrapper }); + stats.sort(function(a, b) { return a.pageNumber - b.pageNumber}); + clear(this.panel); + for (var i = 0, ii = stats.length; i < ii; ++i) + this.panel.appendChild(stats[i].div); + } + }; +})(); + // Manages all the debugging tools. var PDFBug = (function PDFBugClosure() { var panelWidth = 300; @@ -327,8 +379,29 @@ var PDFBug = (function PDFBugClosure() { return { tools: [ FontInspector, - StepperManager + StepperManager, + Stats ], + enable: function(ids) { + var all = false, tools = this.tools; + if (ids.length === 1 && ids[0] === 'all') + all = true; + for (var i = 0; i < tools.length; ++i) { + var tool = tools[i]; + if (all || ids.indexOf(tool.id) !== -1) + tool.enabled = true; + } + if (!all) { + // Sort the tools by the order they are enabled. + tools.sort(function(a, b) { + var indexA = ids.indexOf(a.id); + indexA = indexA < 0 ? tools.length : indexA; + var indexB = ids.indexOf(b.id); + indexB = indexB < 0 ? tools.length : indexB; + return indexA - indexB; + }); + } + }, init: function init() { /* * Basic Layout: diff --git a/web/viewer.css b/web/viewer.css index befaf7aa6..fdce0288a 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -65,16 +65,6 @@ body { line-height: 16px; } -span#info { - display: none; -} - -@-moz-document regexp("http:.*debug=1.*") { - span#info { - display: inline-block; - } -} - /* === Sidebar === */ #sidebar { position: fixed; @@ -442,3 +432,11 @@ canvas { background: yellow; opacity: 0.3; } +#PDFBug .stats { + font-size: 10px; + white-space: pre; + font-family: courier; +} +#PDFBug .stats .title { + font-weight: bold; +} diff --git a/web/viewer.html b/web/viewer.html index 3d1b3c4b7..34b2e77cb 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -102,7 +102,6 @@ Bookmark - --