diff --git a/LICENSE b/LICENSE index a3e99545a..f01ded412 100644 --- a/LICENSE +++ b/LICENSE @@ -12,6 +12,7 @@ Jakob Miland Artur Adib Brendan Dahl + David Quintana Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/Makefile b/Makefile index 62565670a..3cc423350 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ PDF_JS_FILES = \ ../external/jpgjs/jpg.js \ jpx.js \ bidi.js \ + metadata.js \ $(NULL) # make server diff --git a/README.md b/README.md index 51a7f4e98..2c706f4ac 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # PDF.JS - + pdf.js is an HTML5 technology experiment that explores building a faithful and efficient Portable Document Format (PDF) renderer without native code diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 9375a2690..4467abc6b 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -30,23 +30,11 @@ function log(aMsg) { Services.console.logStringMessage(msg); dump(msg + '\n'); } -function getWindow(top, id) { - return top.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .getOuterWindowWithId(id); -} -function windowID(win) { - return win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .outerWindowID; -} -function topWindow(win) { - return win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); + +function getDOMWindow(aChannel) { + var requestor = aChannel.notificationCallbacks; + var win = requestor.getInterface(Components.interfaces.nsIDOMWindow); + return win; } // All the priviledged actions. @@ -75,6 +63,7 @@ ChromeActions.prototype = { } }; + // Event listener to trigger chrome privedged code. function RequestListener(actions) { this.actions = actions; @@ -163,38 +152,32 @@ PdfStreamConverter.prototype = { var channel = ioService.newChannel( 'resource://pdf.js/web/viewer.html', null, null); + var listener = this.listener; + // Proxy all the requst observer calls, when it gets to onStopRequst + // we can get the dom window. + var proxy = { + onStartRequest: function() { + listener.onStartRequest.apply(listener, arguments); + }, + onDataAvailable: function() { + listener.onDataAvailable.apply(listener, arguments); + }, + onStopRequest: function() { + var domWindow = getDOMWindow(channel); + // Double check the url is still the correct one. + if (domWindow.document.documentURIObject.equals(aRequest.URI)) { + let requestListener = new RequestListener(new ChromeActions); + domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { + requestListener.receive(event); + }, false, true); + } + listener.onStopRequest.apply(listener, arguments); + } + }; + // Keep the URL the same so the browser sees it as the same. channel.originalURI = aRequest.URI; - channel.asyncOpen(this.listener, aContext); - - // Setup a global listener waiting for the next DOM to be created and verfiy - // that its the one we want by its URL. When the correct DOM is found create - // an event listener on that window for the pdf.js events that require - // chrome priviledges. Code snippet from John Galt. - let window = aRequest.loadGroup.groupObserver - .QueryInterface(Ci.nsIWebProgress) - .DOMWindow; - let top = topWindow(window); - let id = windowID(window); - window = null; - - top.addEventListener('DOMWindowCreated', function onDOMWinCreated(event) { - let doc = event.originalTarget; - let win = doc.defaultView; - - if (id == windowID(win)) { - top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true); - if (!doc.documentURIObject.equals(aRequest.URI)) - return; - - let requestListener = new RequestListener(new ChromeActions); - win.addEventListener(PDFJS_EVENT_ID, function(event) { - requestListener.receive(event); - }, false, true); - } else if (!getWindow(top, id)) { - top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true); - } - }, true); + channel.asyncOpen(proxy, aContext); }, // nsIRequestObserver::onStopRequest diff --git a/external/shelljs/ChangeLog b/external/shelljs/ChangeLog new file mode 100644 index 000000000..23ca5d0e0 --- /dev/null +++ b/external/shelljs/ChangeLog @@ -0,0 +1,22 @@ +2012.03.22, Version 0.0.4 + +* ls() and find() return arrays instead of hashes (Artur Adib) +* exec({silent:...}) overrides global silent() state (Artur Adib) + + +2012.03.21, Version 0.0.3 + +* Wildcard bug fix (Artur Adib) +* execSync() now uses dummy file I/O op to reduce CPU usage (Artur Adib) +* Minor fixes + + +2012.03.15, Version 0.0.2 + +* New methods: find(), test() (Artur Adib) +* Deprecated non-Unix methods: exists(), verbose() + + +2012.03.03, Version 0.0.2pre1 + +* First public release diff --git a/external/shelljs/README.md b/external/shelljs/README.md index 82f53533b..af5f05127 100644 --- a/external/shelljs/README.md +++ b/external/shelljs/README.md @@ -1,6 +1,7 @@ # 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._ ++ _This project is young and experimental. Use at your own risk._ ++ _Major API change as of v0.0.4: `ls()` and `find()` now return arrays._ 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. @@ -18,11 +19,11 @@ cp('-R', 'stuff/*', 'out/Release'); // Replace macros in each .js file cd('lib'); -for (file in ls('*.js')) { +ls('*.js').forEach(function(file) { sed('-i', 'BUILD_VERSION', 'v0.1.2', file); sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file); sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file); -} +}); cd('..'); // Run external tool synchronously @@ -73,11 +74,11 @@ target.docs = function() { cd(__dirname); mkdir('docs'); cd('lib'); - for (file in ls('*.js')) { + ls('*.js').forEach(function(file){ var text = grep('//@', file); // extract special comments text.replace('//@', ''); // remove comment tags text.to('docs/my_docs.md'); - } + }); } ``` @@ -128,9 +129,7 @@ ls('-R', '/users/me', '/tmp'); ls('-R', ['/users/me', '/tmp']); // same as above ``` -Returns list of files in the given path, or in current directory if no path provided. -For convenient iteration via `for (file in ls())`, the format returned is a hash object: -`{ 'file1':null, 'dir1/file2':null, ...}`. +Returns array of files in the given path, or in current directory if no path provided. #### find(path [,path ...]) #### find(path_array) @@ -139,18 +138,12 @@ 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' -} +find('.').filter(function(file) { return file.match(/\.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, ...}`. +Returns array of all files (however deep) in the given paths. -The main difference with respect to `ls('-R', path)` is that the resulting file names +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`. #### cp('[options ,] source [,source ...], dest') @@ -332,6 +325,10 @@ When in synchronous mode returns the object `{ code:..., output:... }`, containi `output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the arguments `(code, output)`. +**Note:** For long-lived processes, it's best to run `exec()` asynchronously as +the current synchronous implementation uses a lot of CPU. This should be getting +fixed soon. + ## Non-Unix commands @@ -339,16 +336,35 @@ arguments `(code, output)`. Searches and returns string containing a writeable, platform-dependent temporary directory. Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir). -#### exists(path [, path ...]) -#### exists(path_array) -Returns true if all the given paths exist. - #### error() Tests if error occurred in the last command. Returns `null` if no error occurred, otherwise returns string explaining the error -#### verbose() -Enables all output (default) +#### silent([state]) +Example: -#### silent() -Suppresses all output, except for explict `echo()` calls +```javascript +var silentState = silent(); +silent(true); +/* ... */ +silent(silentState); // restore old silent state +``` + +Suppresses all command output if `state = true`, except for `echo()` calls. +Returns state if no arguments given. + +## Deprecated + + +#### exists(path [, path ...]) +#### exists(path_array) + +_This function is being deprecated. Use `test()` instead._ + +Returns true if all the given paths exist. + +#### verbose() + +_This function is being deprecated. Use `silent(false) instead.`_ + +Enables all output (default) diff --git a/external/shelljs/make.js b/external/shelljs/make.js index 9c92dadc8..c495735bf 100644 --- a/external/shelljs/make.js +++ b/external/shelljs/make.js @@ -23,7 +23,7 @@ setTimeout(function() { if (oldTarget.done && !force) return; oldTarget.done = true; - return oldTarget(arguments); + return oldTarget.apply(oldTarget, arguments); } })(t, target[t]); diff --git a/external/shelljs/package.json b/external/shelljs/package.json index 9499680e9..4d2830cb9 100644 --- a/external/shelljs/package.json +++ b/external/shelljs/package.json @@ -1,12 +1,29 @@ -{ "name": "shelljs" -, "version": "0.0.2pre1" -, "author": "Artur Adib " -, "description": "Portable Unix shell commands for Node.js" -, "keywords": ["unix", "shell", "makefile", "make", "jake", "synchronous"] -, "repository": "git://github.com/arturadib/shelljs" -, "homepage": "http://github.com/arturadib/shelljs" -, "main": "./shell.js" -, "scripts": { +{ + "name": "shelljs", + "version": "0.0.5pre4", + "author": "Artur Adib ", + "description": "Portable Unix shell commands for Node.js", + "keywords": [ + "unix", + "shell", + "makefile", + "make", + "jake", + "synchronous" + ], + "repository": { + "type": "git", + "url": "git://github.com/arturadib/shelljs.git" + }, + "homepage": "http://github.com/arturadib/shelljs", + "main": "./shell.js", + "scripts": { "test": "node scripts/run-tests" + }, + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {}, + "engines": { + "node": "*" } } diff --git a/external/shelljs/shell.js b/external/shelljs/shell.js index 95ddaa067..92c49c54d 100644 --- a/external/shelljs/shell.js +++ b/external/shelljs/shell.js @@ -74,9 +74,7 @@ exports.pwd = wrap('pwd', _pwd); //@ ls('-R', ['/users/me', '/tmp']); // same as above //@ ``` //@ -//@ Returns list of files in the given path, or in current directory if no path provided. -//@ For convenient iteration via `for (file in ls())`, the format returned is a hash object: -//@ `{ 'file1':null, 'dir1/file2':null, ...}`. +//@ Returns array of files in the given path, or in current directory if no path provided. function _ls(options, paths) { options = parseOptions(options, { 'R': 'recursive', @@ -90,24 +88,30 @@ function _ls(options, paths) { else if (typeof paths === 'string') paths = [].slice.call(arguments, 1); - var hash = {}; + var list = []; - function pushHash(file, query) { + // Conditionally pushes file to list - returns true if pushed, false otherwise + // (e.g. prevents hidden files to be included unless explicitly told so) + function pushFile(file, query) { // hidden file? if (path.basename(file)[0] === '.') { // not explicitly asking for hidden files? if (!options.all && !(path.basename(query)[0] === '.' && path.basename(query).length > 1)) - return; + return false; } - hash[file] = null; + if (platform === 'win') + file = file.replace(/\\/g, '/'); + + list.push(file); + return true; } paths.forEach(function(p) { if (fs.existsSync(p)) { // Simple file? if (fs.statSync(p).isFile()) { - pushHash(p, p); + pushFile(p, p); return; // continue } @@ -115,14 +119,17 @@ function _ls(options, paths) { if (fs.statSync(p).isDirectory()) { // Iterate over p contents fs.readdirSync(p).forEach(function(file) { - pushHash(file, p); + if (!pushFile(file, p)) + return; - // Recursive - var oldDir = _pwd(); - _cd('', p); - if (fs.statSync(file).isDirectory() && options.recursive) - hash = extend(hash, _ls('-R', file+'/*')); - _cd('', oldDir); + // Recursive? + if (options.recursive) { + var oldDir = _pwd(); + _cd('', p); + if (fs.statSync(file).isDirectory()) + list = list.concat(_ls('-R'+(options.all?'a':''), file+'/*')); + _cd('', oldDir); + } }); return; // continue } @@ -137,17 +144,20 @@ function _ls(options, paths) { // Escape special regular expression chars var regexp = basename.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\.|\+|\?)/g, '\\$1'); // Translates wildcard into regex - regexp = '^' + regexp.replace(/\*/g, '.*'); + regexp = '^' + regexp.replace(/\*/g, '.*') + '$'; // Iterate over directory contents fs.readdirSync(dirname).forEach(function(file) { if (file.match(new RegExp(regexp))) { - pushHash(path.normalize(dirname+'/'+file), basename); + if (!pushFile(path.normalize(dirname+'/'+file), basename)) + return; - // Recursive - var pp = dirname + '/' + file; - if (fs.statSync(pp).isDirectory() && options.recursive) - hash = extend(hash, _ls('-R', pp+'/*')); - } + // Recursive? + if (options.recursive) { + var pp = dirname + '/' + file; + if (fs.statSync(pp).isDirectory()) + list = list.concat(_ls('-R'+(options.all?'a':''), pp+'/*')); + } // recursive + } // if file matches }); // forEach return; } @@ -155,7 +165,7 @@ function _ls(options, paths) { error('no such file or directory: ' + p, true); }); - return hash; + return list; }; exports.ls = wrap('ls', _ls); @@ -168,16 +178,10 @@ exports.ls = wrap('ls', _ls); //@ ```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' -//@ } +//@ find('.').filter(function(file) { return file.match(/\.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, ...}`. +//@ Returns array of all files (however deep) in the given paths. //@ //@ 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`. @@ -189,21 +193,28 @@ function _find(options, paths) { else if (typeof paths === 'string') paths = [].slice.call(arguments, 1); - var hash = {}; + var list = []; + + function pushFile(file) { + if (platform === 'win') + file = file.replace(/\\/g, '/'); + list.push(file); + } // 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; + paths.forEach(function(file) { + pushFile(file); if (fs.statSync(file).isDirectory()) { - for (subfile in _ls('-Ra', file+'/*')) - hash[subfile] = null; + _ls('-Ra', file+'/*').forEach(function(subfile) { + pushFile(subfile); + }); } }); - return hash; + return list; } exports.find = wrap('find', _find); @@ -347,9 +358,20 @@ function _rm(options, files) { // Remove simple file if (fs.statSync(file).isFile()) { - fs.unlinkSync(file); + + // Do not check for file writing permissions + if (options.force) { + _unlinkSync(file); + return; + } + + if (isWriteable(file)) + _unlinkSync(file); + else + error('permission denied: '+file, true); + return; - } + } // simple file // Path is an existing directory, but no -r flag given if (fs.statSync(file).isDirectory() && !options.recursive) { @@ -359,7 +381,7 @@ function _rm(options, files) { // Recursively remove existing directory if (fs.statSync(file).isDirectory() && options.recursive) { - rmdirSyncRecursive(file); + rmdirSyncRecursive(file, options.force); } }); // forEach(file) }; // rm @@ -582,7 +604,11 @@ function _to(options, file) { if (!fs.existsSync( path.dirname(file) )) error('no such file or directory: ' + path.dirname(file)); - fs.writeFileSync(file, this.toString(), 'utf8'); + try { + fs.writeFileSync(file, this.toString(), 'utf8'); + } catch(e) { + error('could not write to file (code '+e.code+'): '+file, true); + } }; // In the future, when Proxies are default, we can add methods like `.to()` to primitive strings. // For now, this is a dummy function to bookmark places we need such strings @@ -751,7 +777,7 @@ exports.which = wrap('which', _which); //@ like `.to()`. function _echo(options) { var messages = [].slice.call(arguments, 1); - log.apply(this, messages); + console.log.apply(this, messages); return ShellString(messages.join(' ')); }; exports.echo = wrap('echo', _echo); @@ -783,6 +809,10 @@ exports.env = process.env; //@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's //@ `output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the //@ arguments `(code, output)`. +//@ +//@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as +//@ the current synchronous implementation uses a lot of CPU. This should be getting +//@ fixed soon. function _exec(command, options, callback) { if (!command) error('must specify command'); @@ -793,7 +823,7 @@ function _exec(command, options, callback) { } options = extend({ - silent: false, + silent: state.silent, async: false }, options); @@ -822,11 +852,53 @@ exports.exec = wrap('exec', _exec, {notUnix:true}); //@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir). exports.tempdir = wrap('tempdir', tempDir); + +//@ +//@ #### error() +//@ Tests if error occurred in the last command. Returns `null` if no error occurred, +//@ otherwise returns string explaining the error +exports.error = function() { + return state.error; +} + +//@ +//@ #### silent([state]) +//@ Example: +//@ +//@ ```javascript +//@ var silentState = silent(); +//@ silent(true); +//@ /* ... */ +//@ silent(silentState); // restore old silent state +//@ ``` +//@ +//@ Suppresses all command output if `state = true`, except for `echo()` calls. +//@ Returns state if no arguments given. +exports.silent = function(_state) { + if (typeof _state !== 'boolean') + return state.silent; + + state.silent = _state; +} + + +//@ +//@ ## Deprecated +//@ + + + + //@ //@ #### exists(path [, path ...]) //@ #### exists(path_array) +//@ +//@ _This function is being deprecated. Use `test()` instead._ +//@ //@ Returns true if all the given paths exist. function _exists(options, paths) { + deprecate('exists', 'Use test() instead.'); + if (!paths) error('no paths given'); @@ -844,32 +916,19 @@ function _exists(options, paths) { }; exports.exists = wrap('exists', _exists); -//@ -//@ #### error() -//@ Tests if error occurred in the last command. Returns `null` if no error occurred, -//@ otherwise returns string explaining the error -exports.error = function() { - return state.error; -} //@ //@ #### verbose() +//@ +//@ _This function is being deprecated. Use `silent(false) instead.`_ +//@ //@ Enables all output (default) exports.verbose = function() { + deprecate('verbose', 'Use silent(false) instead.'); + state.silent = false; } -//@ -//@ #### silent() -//@ Suppresses all output, except for explict `echo()` calls -exports.silent = function() { - state.silent = true; -} - - - - - @@ -889,6 +948,10 @@ function log() { console.log.apply(this, arguments); } +function deprecate(what, msg) { + console.log('*** ShellJS.'+what+': This function is deprecated.', msg); +} + function write(msg) { if (!state.silent) process.stdout.write(msg); @@ -962,7 +1025,7 @@ function wrap(cmd, fn, options) { } catch (e) { if (!state.error) { // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug... - console.log('maker.js: internal error'); + console.log('shell.js: internal error'); console.log(e.stack || e); process.exit(1); } @@ -970,7 +1033,7 @@ function wrap(cmd, fn, options) { throw e; } - state.currentCmd = 'maker.js'; + state.currentCmd = 'shell.js'; return retValue; } } // wrap @@ -984,10 +1047,22 @@ function copyFileSync(srcFile, destFile) { var BUF_LENGTH = 64*1024, buf = new Buffer(BUF_LENGTH), - fdr = fs.openSync(srcFile, 'r'), - fdw = fs.openSync(destFile, 'w'), bytesRead = BUF_LENGTH, - pos = 0; + pos = 0, + fdr = null, + fdw = null; + + try { + fdr = fs.openSync(srcFile, 'r'); + } catch(e) { + error('copyFileSync: could not read src file ('+srcFile+')'); + } + + try { + fdw = fs.openSync(destFile, 'w'); + } catch(e) { + error('copyFileSync: could not write to dest file (code='+e.code+'):'+destFile); + } while (bytesRead === BUF_LENGTH) { bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos); @@ -1050,28 +1125,41 @@ function cpdirSyncRecursive(sourceDir, destDir, opts) { // // Licensed under the MIT License // http://www.opensource.org/licenses/mit-license.php -function rmdirSyncRecursive(dir) { +function rmdirSyncRecursive(dir, force) { var files; files = fs.readdirSync(dir); // Loop through and delete everything in the sub-tree after checking it for(var i = 0; i < files.length; i++) { - var currFile = fs.lstatSync(dir + "/" + files[i]); + var file = dir + "/" + files[i], + currFile = fs.lstatSync(file); - if(currFile.isDirectory()) // Recursive function back to the beginning - rmdirSyncRecursive(dir + "/" + files[i]); + if(currFile.isDirectory()) { // Recursive function back to the beginning + rmdirSyncRecursive(file, force); + } - else if(currFile.isSymbolicLink()) // Unlink symlinks - fs.unlinkSync(dir + "/" + files[i]); + else if(currFile.isSymbolicLink()) { // Unlink symlinks + if (force || isWriteable(file)) + _unlinkSync(file); + } else // Assume it's a file - perhaps a try/catch belongs here? - fs.unlinkSync(dir + "/" + files[i]); + if (force || isWriteable(file)) + _unlinkSync(file); } // Now that we know everything in the sub-tree has been deleted, we can delete the main directory. // Huzzah for the shopkeep. - return fs.rmdirSync(dir); + + var result; + try { + result = fs.rmdirSync(dir); + } catch(e) { + error('could not remove directory (code '+e.code+'): ' + dir, true); + } + + return result; }; // rmdirSyncRecursive // Recursively creates 'dir' @@ -1118,7 +1206,7 @@ function writeableDir(dir) { var testFile = dir+'/'+randomFileName(); try { fs.writeFileSync(testFile, ' '); - fs.unlinkSync(testFile); + _unlinkSync(testFile); return dir; } catch (e) { return false; @@ -1151,6 +1239,10 @@ function tempDir() { // Wrapper around exec() to enable echoing output to console in real time function execAsync(cmd, opts, callback) { var output = ''; + + var options = extend({ + silent: state.silent + }, opts); var c = child.exec(cmd, {env: process.env}, function(err) { if (callback) @@ -1159,14 +1251,14 @@ function execAsync(cmd, opts, callback) { c.stdout.on('data', function(data) { output += data; - if (!opts.silent) - write(data); + if (!options.silent) + process.stdout.write(data); }); c.stderr.on('data', function(data) { output += data; - if (!opts.silent) - write(data); + if (!options.silent) + process.stdout.write(data); }); } @@ -1178,16 +1270,17 @@ function execAsync(cmd, opts, callback) { function execSync(cmd, opts) { var stdoutFile = path.resolve(tempDir()+'/'+randomFileName()), codeFile = path.resolve(tempDir()+'/'+randomFileName()), - scriptFile = path.resolve(tempDir()+'/'+randomFileName()); + scriptFile = path.resolve(tempDir()+'/'+randomFileName()), + sleepFile = path.resolve(tempDir()+'/'+randomFileName()); var options = extend({ - silent: false + silent: state.silent }, opts); var previousStdoutContent = ''; // Echoes stdout changes from running process, if not silent function updateStdout() { - if (state.silent || options.silent || !fs.existsSync(stdoutFile)) + if (options.silent || !fs.existsSync(stdoutFile)) return; var stdoutContent = fs.readFileSync(stdoutFile, 'utf8'); @@ -1214,9 +1307,9 @@ function execSync(cmd, opts) { fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0'); \ });"; - if (fs.existsSync(scriptFile)) fs.unlinkSync(scriptFile); - if (fs.existsSync(stdoutFile)) fs.unlinkSync(stdoutFile); - if (fs.existsSync(codeFile)) fs.unlinkSync(codeFile); + if (fs.existsSync(scriptFile)) _unlinkSync(scriptFile); + if (fs.existsSync(stdoutFile)) _unlinkSync(stdoutFile); + if (fs.existsSync(codeFile)) _unlinkSync(codeFile); fs.writeFileSync(scriptFile, script); child.exec('node '+scriptFile, { @@ -1225,8 +1318,11 @@ function execSync(cmd, opts) { }); // The wait loop - while (!fs.existsSync(codeFile)) { updateStdout(); }; - while (!fs.existsSync(stdoutFile)) { updateStdout(); }; + // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage + // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing + // CPU usage, though apparently not so much on Windows) + while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }; + while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }; // At this point codeFile exists, but it's not necessarily flushed yet. // Keep reading it until it is. @@ -1236,10 +1332,12 @@ function execSync(cmd, opts) { var stdout = fs.readFileSync(stdoutFile, 'utf8'); - fs.unlinkSync(scriptFile); - fs.unlinkSync(stdoutFile); - fs.unlinkSync(codeFile); - + // No biggie if we can't erase the files now -- they're in a temp dir anyway + try { _unlinkSync(scriptFile); } catch(e) {}; + try { _unlinkSync(stdoutFile); } catch(e) {}; + try { _unlinkSync(codeFile); } catch(e) {}; + try { _unlinkSync(sleepFile); } catch(e) {}; + // True if successful, false if not var obj = { code: code, @@ -1257,8 +1355,9 @@ function expand(list) { list.forEach(function(listEl) { // Wildcard present? if (listEl.search(/\*/) > -1) { - for (file in _ls('', listEl)) + _ls('', listEl).forEach(function(file) { expanded.push(file); + }); } else { expanded.push(listEl); } @@ -1290,3 +1389,33 @@ function extend(target) { return target; } + +// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e. +// file can be unlinked even if it's read-only, see joyent/node#3006 +function _unlinkSync(file) { + try { + fs.unlinkSync(file); + } catch(e) { + // Try to override file permission + if (e.code === 'EPERM') { + fs.chmodSync(file, '0666'); + fs.unlinkSync(file); + } else { + throw e; + } + } +} + +// Hack to determine if file has write permissions for current user +// Avoids having to check user, group, etc, but it's probably slow +function isWriteable(file) { + var writePermission = true; + try { + var __fd = fs.openSync(file, 'a'); + fs.closeSync(__fd); + } catch(e) { + writePermission = false; + } + + return writePermission; +} diff --git a/make.js b/make.js index 33771aeb7..e0975fec8 100755 --- a/make.js +++ b/make.js @@ -97,9 +97,10 @@ target.bundle = function() { 'worker.js', '../external/jpgjs/jpg.js', 'jpx.js', - 'bidi.js']; + 'bidi.js', + 'metadata.js']; - if (!exists(BUILD_DIR)) + if (!test('-d', BUILD_DIR)) mkdir(BUILD_DIR); cd('src'); @@ -142,10 +143,10 @@ target.pagesrepo = function() { echo(); echo('### Creating fresh clone of gh-pages'); - if (!exists(BUILD_DIR)) + if (!test('-d', BUILD_DIR)) mkdir(BUILD_DIR); - if (!exists(GH_PAGES_DIR)) { + if (!test('-d', GH_PAGES_DIR)) { echo(); echo('Cloning project repo...'); echo('(This operation can take a while, depending on network conditions)'); @@ -277,10 +278,10 @@ 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)) { + find(FIREFOX_BUILD_DIR).forEach(function(file) { if (file.match(/^\./)) rm('-f', file); - } + }); // Update the build version number sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/install.rdf'); @@ -304,10 +305,10 @@ target.firefox = function() { // List all files for mozilla-central cd(FIREFOX_BUILD_DIR); var extensionFiles = ''; - for (file in find(FIREFOX_MC_EXTENSION_FILES)) { + find(FIREFOX_MC_EXTENSION_FILES).forEach(function(file){ if (test('-f', file)) extensionFiles += file+'\n'; - } + }); extensionFiles.to('extension-files'); }; @@ -360,10 +361,19 @@ target.test = function() { target.unittest(); }; +// +// make bottest +// (Special tests for the Github bot) +// +target.bottest = function() { + target.browsertest({noreftest: true}); + // target.unittest(); +}; + // // make browsertest // -target.browsertest = function() { +target.browsertest = function(options) { cd(ROOT_DIR); echo(); echo('### Running browser tests'); @@ -371,14 +381,16 @@ target.browsertest = function() { var PDF_TEST = env['PDF_TEST'] || 'test_manifest.json', PDF_BROWSERS = env['PDF_BROWSERS'] || 'resources/browser_manifests/browser_manifest.json'; - if (!exists('test/' + PDF_BROWSERS)) { + if (!test('-f', 'test/' + PDF_BROWSERS)) { echo('Browser manifest file test/' + PDF_BROWSERS + ' does not exist.'); echo('Try copying one of the examples in test/resources/browser_manifests/'); exit(1); } + var reftest = (options && options.noreftest) ? '' : '--reftest'; + cd('test'); - exec(PYTHON_BIN + ' test.py --reftest --browserManifestFile=' + PDF_BROWSERS + + exec(PYTHON_BIN + ' -u test.py '+reftest+' --browserManifestFile=' + PDF_BROWSERS + ' --manifestFile=' + PDF_TEST, {async: true}); }; @@ -390,10 +402,37 @@ target.unittest = function() { echo(); echo('### Running unit tests'); + if (!which('make')) { + echo('make not found. Skipping unit tests...'); + return; + } + cd('test/unit'); exec('make', {async: true}); }; +// +// make botmakeref +// +target.botmakeref = function() { + cd(ROOT_DIR); + echo(); + echo('### Creating reference images'); + + var PDF_TEST = env['PDF_TEST'] || 'test_manifest.json', + PDF_BROWSERS = env['PDF_BROWSERS'] || 'resources/browser_manifests/browser_manifest.json'; + + if (!test('-f', 'test/' + PDF_BROWSERS)) { + echo('Browser manifest file test/' + PDF_BROWSERS + ' does not exist.'); + echo('Try copying one of the examples in test/resources/browser_manifests/'); + exit(1); + } + + cd('test'); + exec(PYTHON_BIN + ' -u test.py --masterMode --noPrompts --browserManifestFile=' + PDF_BROWSERS, + {async: true}); +}; + /////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/src/bidi.js b/src/bidi.js index aab477dbc..5f18e5303 100644 --- a/src/bidi.js +++ b/src/bidi.js @@ -132,9 +132,9 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // get types, fill arrays - var chars = new Array(strLength); - var types = new Array(strLength); - var oldtypes = new Array(strLength); + var chars = []; + var types = []; + var oldtypes = []; var numBidi = 0; for (var i = 0; i < strLength; ++i) { @@ -176,16 +176,12 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } } - var levels = new Array(strLength); + var levels = []; 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. */ diff --git a/src/canvas.js b/src/canvas.js index 7bf94a642..e915c4a84 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -756,24 +756,26 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var charWidth = glyph.width * fontSize * 0.001 + Util.sign(current.fontMatrix[0]) * charSpacing; - var scaledX = x / fontSizeScale; - switch (textRenderingMode) { - default: // other unsupported rendering modes - case TextRenderingMode.FILL: - case TextRenderingMode.FILL_ADD_TO_PATH: - ctx.fillText(char, scaledX, 0); - break; - case TextRenderingMode.STROKE: - case TextRenderingMode.STROKE_ADD_TO_PATH: - ctx.strokeText(char, scaledX, 0); - break; - case TextRenderingMode.FILL_STROKE: - case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: - ctx.fillText(char, scaledX, 0); - ctx.strokeText(char, scaledX, 0); - break; - case TextRenderingMode.INVISIBLE: - break; + if (!glyph.disabled) { + var scaledX = x / fontSizeScale; + switch (textRenderingMode) { + default: // other unsupported rendering modes + case TextRenderingMode.FILL: + case TextRenderingMode.FILL_ADD_TO_PATH: + ctx.fillText(char, scaledX, 0); + break; + case TextRenderingMode.STROKE: + case TextRenderingMode.STROKE_ADD_TO_PATH: + ctx.strokeText(char, scaledX, 0); + break; + case TextRenderingMode.FILL_STROKE: + case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: + ctx.fillText(char, scaledX, 0); + ctx.strokeText(char, scaledX, 0); + break; + case TextRenderingMode.INVISIBLE: + break; + } } x += charWidth; diff --git a/src/colorspace.js b/src/colorspace.js index d3d392361..e1df7c725 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -220,7 +220,7 @@ var AlternateCS = (function AlternateCSClosure() { var baseNumComps = base.numComps; var baseBuf = new Uint8Array(baseNumComps * length); var numComps = this.numComps; - var scaled = new Array(numComps); + var scaled = []; for (var i = 0; i < length; i += numComps) { for (var z = 0; z < numComps; ++z) diff --git a/src/core.js b/src/core.js index ecc2c94a5..3010e9f6b 100644 --- a/src/core.js +++ b/src/core.js @@ -587,14 +587,6 @@ var PDFDocModel = (function PDFDocModelClosure() { this.mainXRefEntriesOffset); this.xref = xref; this.catalog = new Catalog(xref); - if (xref.trailer && xref.trailer.has('ID')) { - var fileID = ''; - var id = xref.fetchIfRef(xref.trailer.get('ID'))[0]; - id.split('').forEach(function(el) { - fileID += Number(el.charCodeAt(0)).toString(16); - }); - this.fileID = fileID; - } }, get numPages() { var linearization = this.linearization; @@ -602,21 +594,33 @@ var PDFDocModel = (function PDFDocModelClosure() { // shadow the prototype getter return shadow(this, 'numPages', num); }, + getDocumentInfo: function pdfDocGetDocumentInfo() { + var info; + if (this.xref.trailer.has('Info')) + info = this.xref.fetch(this.xref.trailer.get('Info')); + + return shadow(this, 'getDocumentInfo', info); + }, getFingerprint: function pdfDocGetFingerprint() { - if (this.fileID) { - return this.fileID; + var xref = this.xref, fileID; + if (xref.trailer.has('ID')) { + fileID = ''; + var id = xref.fetchIfRef(xref.trailer.get('ID'))[0]; + id.split('').forEach(function(el) { + fileID += Number(el.charCodeAt(0)).toString(16); + }); } else { // If we got no fileID, then we generate one, // from the first 100 bytes of PDF var data = this.stream.bytes.subarray(0, 100); var hash = calculateMD5(data, 0, data.length); - var strHash = ''; + fileID = ''; for (var i = 0, length = hash.length; i < length; i++) { - strHash += Number(hash[i]).toString(16); + fileID += Number(hash[i]).toString(16); } - - return strHash; } + + return shadow(this, 'getFingerprint', fileID); }, getPage: function pdfDocGetPage(n) { return this.catalog.getPage(n); @@ -645,6 +649,7 @@ var PDFDoc = (function PDFDocClosure() { this.stream = stream; this.pdfModel = new PDFDocModel(stream); this.fingerprint = this.pdfModel.getFingerprint(); + this.info = this.pdfModel.getDocumentInfo(); this.catalog = this.pdfModel.catalog; this.objs = new PDFObjects(); @@ -785,7 +790,7 @@ var PDFDoc = (function PDFDocClosure() { error('Only 3 component or 1 component can be returned'); var img = new Image(); - img.onload = (function jpegImageLoaderOnload() { + img.onload = (function messageHandler_onloadClosure() { var width = img.width; var height = img.height; var size = width * height; diff --git a/src/evaluator.js b/src/evaluator.js index 1d83c380b..0a4db37e8 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -805,6 +805,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0; var lastChar = xref.fetchIfRef(dict.get('LastChar')) || maxCharIndex; var fontName = xref.fetchIfRef(descriptor.get('FontName')); + // Some bad pdf's have a string as the font name. + if (isString(fontName)) + fontName = new Name(fontName); assertWellFormed(isName(fontName), 'invalid font name'); var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); diff --git a/src/fonts.js b/src/fonts.js index 6b8906827..cdff1f980 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -174,7 +174,6 @@ var Encodings = { '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', 'oslash', 'oe', 'germandbls'], WinAnsiEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', @@ -839,7 +838,7 @@ var Font = (function FontClosure() { var subtype = properties.subtype; var cff = (subtype == 'Type1C' || subtype == 'CIDFontType0C') ? - new Type2CFF(file, properties) : new CFF(name, file, properties); + new CFFFont(file, properties) : new Type1Font(name, file, properties); // Wrap the CFF data inside an OTF font file data = this.convert(name, cff, properties); @@ -1658,6 +1657,30 @@ var Font = (function FontClosure() { glyf.data = newGlyfData.subarray(0, writeOffset); } + function findEmptyGlyphs(locaTable, isGlyphLocationsLong, emptyGlyphIds) { + var itemSize, itemDecode; + if (isGlyphLocationsLong) { + itemSize = 4; + itemDecode = function fontItemDecodeLong(data, offset) { + return (data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]; + }; + } else { + itemSize = 2; + itemDecode = function fontItemDecode(data, offset) { + return (data[offset] << 9) | (data[offset + 1] << 1); + }; + } + var data = locaTable.data, length = data.length; + var lastOffset = itemDecode(data, 0); + for (var i = itemSize, j = 0; i < length; i += itemSize, j++) { + var offset = itemDecode(data, i); + if (offset == lastOffset) + emptyGlyphIds[j] = true; + lastOffset = offset; + } + } + function readGlyphNameMap(post, properties) { var start = (font.start ? font.start : 0) + post.offset; font.pos = start; @@ -1784,11 +1807,15 @@ var Font = (function FontClosure() { sanitizeMetrics(font, hhea, hmtx, numGlyphs); sanitizeMetrics(font, vhea, vmtx, numGlyphs); + var isGlyphLocationsLong = int16([head.data[50], head.data[51]]); if (head && loca && glyf) { - var isGlyphLocationsLong = int16([head.data[50], head.data[51]]); sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong); } + var emptyGlyphIds = []; + if (glyf) + findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds); + // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth // Sometimes it's 0. That needs to be fixed if (hhea.data[10] == 0 && hhea.data[11] == 0) { @@ -1801,8 +1828,9 @@ var Font = (function FontClosure() { readGlyphNameMap(post, properties); } - // Replace the old CMAP table with a shiny new one + var glyphs, ids; if (properties.type == 'CIDFontType2') { + // Replace the old CMAP table with a shiny new one // Type2 composite fonts map characters directly to glyphs so the cmap // table must be replaced. // canvas fillText will reencode some characters even if the font has a @@ -1834,7 +1862,9 @@ var Font = (function FontClosure() { } } - var glyphs = [], ids = []; + glyphs = []; + ids = []; + var usedUnicodes = []; var unassignedUnicodeItems = []; for (var i = 1; i < numGlyphs; i++) { @@ -1865,11 +1895,12 @@ var Font = (function FontClosure() { glyphs.push({ unicode: unicode, code: cid }); ids.push(i); } - cmap.data = createCMapTable(glyphs, ids); } else { var cmapTable = readCMapTable(cmap, font); - var glyphs = cmapTable.glyphs; - var ids = cmapTable.ids; + + glyphs = cmapTable.glyphs; + ids = cmapTable.ids; + var hasShortCmap = !!cmapTable.hasShortCmap; var toFontChar = this.toFontChar; @@ -1920,6 +1951,38 @@ var Font = (function FontClosure() { } } + // remove glyph references outside range of avaialable glyphs or empty + var glyphsRemoved = 0; + for (var i = ids.length - 1; i >= 0; i--) { + if (ids[i] < numGlyphs && + (!emptyGlyphIds[ids[i]] || this.isSymbolicFont)) + continue; + ids.splice(i, 1); + glyphs.splice(i, 1); + glyphsRemoved++; + } + + // checking if it's a "true" symbolic font + if (this.isSymbolicFont) { + var minUnicode = 0xFFFF, maxUnicode = 0; + for (var i = 0, ii = glyphs.length; i < ii; i++) { + var unicode = glyphs[i].unicode; + minUnicode = Math.min(minUnicode, unicode); + maxUnicode = Math.max(maxUnicode, unicode); + } + // high byte must be the same for min and max unicodes + if ((maxUnicode & 0xFF00) != (minUnicode & 0xFF00)) + this.isSymbolicFont = false; + } + + // heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding + // does not set properly + if (glyphsRemoved > 2) { + warn('Switching TrueType encoding to MacRomanEncoding for ' + + this.name + ' font'); + encoding = Encodings.MacRomanEncoding; + } + if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) { // Re-encode short map encoding to unicode -- that simplifies the // resolution of MacRoman encoded glyphs logic for TrueType fonts: @@ -1956,6 +2019,7 @@ var Font = (function FontClosure() { for (var i = 0, ii = glyphs.length; i < ii; i++) reverseMap[glyphs[i].unicode] = i; + var newGlyphUnicodes = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; var changeCode = false; @@ -1968,16 +2032,26 @@ var Font = (function FontClosure() { } if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; - if (!unicode || (unicode in reverseMap)) - continue; // unknown glyph name or its place is taken + if (!unicode || reverseMap[unicode] === i) + continue; // unknown glyph name or in its own place - glyphs[i].unicode = unicode; - reverseMap[unicode] = i; + newGlyphUnicodes[i] = unicode; if (changeCode) toFontChar[code] = unicode; + delete reverseMap[code]; } - this.useToFontChar = true; } + for (var index in newGlyphUnicodes) { + var unicode = newGlyphUnicodes[index]; + if (reverseMap[unicode]) { + // avoiding assigning to the same unicode + glyphs[index].unicode = unusedUnicode++; + continue; + } + glyphs[index].unicode = unicode; + reverseMap[unicode] = index; + } + this.useToFontChar = true; } // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. @@ -1990,18 +2064,18 @@ var Font = (function FontClosure() { this.useToFontChar = true; } - // remove glyph references outside range of avaialable glyphs - for (var i = 0, ii = ids.length; i < ii; i++) { - if (ids[i] >= numGlyphs) - ids[i] = 0; - } - createGlyphNameMap(glyphs, ids, properties); this.glyphNameMap = properties.glyphNameMap; - - cmap.data = createCMapTable(glyphs, ids); } + // Converting glyphs and ids into font's cmap table + cmap.data = createCMapTable(glyphs, ids); + var unicodeIsEnabled = []; + for (var i = 0, ii = glyphs.length; i < ii; i++) { + unicodeIsEnabled[glyphs[i].unicode] = true; + } + this.unicodeIsEnabled = unicodeIsEnabled; + // Rewrite the 'post' table if needed if (requiredTables.indexOf('post') != -1) { tables.push({ @@ -2327,7 +2401,7 @@ var Font = (function FontClosure() { }, charToGlyph: function fonts_charToGlyph(charcode) { - var fontCharCode, width, operatorList; + var fontCharCode, width, operatorList, disabled; var width = this.widths[charcode]; @@ -2400,11 +2474,14 @@ var Font = (function FontClosure() { unicodeChars = String.fromCharCode(unicodeChars); width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier; + disabled = this.unicodeIsEnabled ? + !this.unicodeIsEnabled[fontCharCode] : false; return { fontChar: String.fromCharCode(fontCharCode), unicode: unicodeChars, width: width, + disabled: disabled, operatorList: operatorList }; }, @@ -2974,7 +3051,7 @@ var Type1Parser = function type1Parser() { * The CFF class takes a Type1 file and wrap it into a * 'Compact Font Format' which itself embed Type2 charstrings. */ -var CFFStrings = [ +var CFFStandardStrings = [ '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', @@ -3044,7 +3121,8 @@ var CFFStrings = [ var type1Parser = new Type1Parser(); -var CFF = function cffCFF(name, file, properties) { +// Type1Font is also a CIDFontType0. +var Type1Font = function Type1Font(name, file, properties) { // Get the data block containing glyphs and subrs informations var headerBlock = file.getBytes(properties.length1); type1Parser.extractFontHeader(headerBlock, properties); @@ -3064,8 +3142,8 @@ var CFF = function cffCFF(name, file, properties) { subrs, properties); }; -CFF.prototype = { - createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) { +Type1Font.prototype = { + createCFFIndexHeader: function createCFFIndexHeader(objects, isByte) { // First 2 bytes contains the number of objects contained into this index var count = objects.length; @@ -3101,7 +3179,7 @@ CFF.prototype = { return data; }, - encodeNumber: function cff_encodeNumber(value) { + encodeNumber: function encodeNumber(value) { // some of the fonts has ouf-of-range values // they are just arithmetic overflows // make sanitizer happy @@ -3119,7 +3197,7 @@ CFF.prototype = { } }, - getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs, + getOrderedCharStrings: function type1Font_getOrderedCharStrings(glyphs, properties) { var charstrings = []; var i, length, glyphName; @@ -3145,7 +3223,7 @@ CFF.prototype = { return charstrings; }, - getType2Charstrings: function cff_getType2Charstrings(type1Charstrings) { + getType2Charstrings: function getType2Charstrings(type1Charstrings) { var type2Charstrings = []; var count = type1Charstrings.length; for (var i = 0; i < count; i++) { @@ -3156,7 +3234,7 @@ CFF.prototype = { return type2Charstrings; }, - getType2Subrs: function cff_getType2Subrs(type1Subrs) { + getType2Subrs: function getType2Subrs(type1Subrs) { var bias = 0; var count = type1Subrs.length; if (count < 1240) @@ -3304,7 +3382,7 @@ CFF.prototype = { var count = glyphs.length; for (var i = 0; i < count; i++) { - var index = CFFStrings.indexOf(charstrings[i].glyph); + var index = CFFStandardStrings.indexOf(charstrings[i].glyph); // Some characters like asterikmath && circlecopyrt are // missing from the original strings, for the moment let's // map them to .notdef and see later if it cause any @@ -3373,106 +3451,31 @@ CFF.prototype = { } }; -var Type2CFF = (function Type2CFFClosure() { - // TODO: replace parsing code with the Type2Parser in font_utils.js - function Type2CFF(file, properties) { - var bytes = file.getBytes(); - this.bytes = bytes; +var CFFFont = (function CFFFontClosure() { + function CFFFont(file, properties) { this.properties = properties; - this.data = this.parse(); + var parser = new CFFParser(file, properties); + var cff = parser.parse(); + var compiler = new CFFCompiler(cff); + this.readExtra(cff); + try { + this.data = compiler.compile(); + } catch (e) { + warn('Failed to compile font ' + properties.loadedName); + // There may have just been an issue with the compiler, set the data + // anyway and hope the font loaded. + this.data = file; + } } - Type2CFF.prototype = { - parse: function cff_parse() { - var header = this.parseHeader(); - var properties = this.properties; - - var nameIndex = this.parseIndex(header.endPos); - this.sanitizeName(nameIndex); - - var dictIndex = this.parseIndex(nameIndex.endPos); - if (dictIndex.length != 1) - error('CFF contains more than 1 font'); - - var stringIndex = this.parseIndex(dictIndex.endPos); - var gsubrIndex = this.parseIndex(stringIndex.endPos); - - var strings = this.getStrings(stringIndex); - - var baseDict = this.parseDict(dictIndex.get(0).data); - var topDict = this.getTopDict(baseDict, strings); - - var bytes = this.bytes; - - var privateDict = {}; - var privateInfo = topDict.Private; - if (privateInfo) { - var privOffset = privateInfo[1], privLength = privateInfo[0]; - var privBytes = bytes.subarray(privOffset, privOffset + privLength); - baseDict = this.parseDict(privBytes); - privateDict = this.getPrivDict(baseDict, strings); - } else { - privateDict.defaultWidthX = properties.defaultWidth; - } - - var charStrings = this.parseIndex(topDict.CharStrings); - - var charset, encoding; - var isCIDFont = properties.subtype == 'CIDFontType0C'; - if (isCIDFont) { - charset = ['.notdef']; - for (var i = 1, ii = charStrings.length; i < ii; ++i) - charset.push('glyph' + i); - - encoding = this.parseCidMap(topDict.charset, - charStrings.length); - } else { - charset = this.parseCharsets(topDict.charset, - charStrings.length, strings); - encoding = this.parseEncoding(topDict.Encoding, properties, - strings, charset); - } - - // The font sanitizer does not support CFF encoding with a - // supplement, since the encoding is not really use to map - // between gid to glyph, let's overwrite what is declared in - // the top dictionary to let the sanitizer think the font use - // StandardEncoding, that's a lie but that's ok. - if (encoding.hasSupplement) - bytes[topDict.Encoding] &= 0x7F; - - // The CFF specification state that the 'dotsection' command - // (12, 0) is deprecated and treated as a no-op, but all Type2 - // charstrings processors should support them. Unfortunately - // the font sanitizer don't. As a workaround the sequence (12, 0) - // is replaced by a useless (0, hmoveto). - var count = charStrings.length; - for (var i = 0; i < count; i++) { - var charstring = charStrings.get(i); - - var start = charstring.start; - var data = charstring.data; - var length = data.length; - for (var j = 0; j <= length; j) { - var value = data[j++]; - if (value == 12 && data[j++] == 0) { - bytes[start + j - 2] = 139; - bytes[start + j - 1] = 22; - } else if (value === 28) { - j += 2; - } else if (value >= 247 && value <= 254) { - j++; - } else if (value == 255) { - j += 4; - } - } - } - + CFFFont.prototype = { + readExtra: function readExtra(cff) { // charstrings contains info about glyphs (one element per glyph // containing mappings for {unicode, width}) - var charstrings = this.getCharStrings(charset, encoding.encoding, - privateDict, this.properties); + var charset = cff.charset.charset; + var encoding = cff.encoding ? cff.encoding.encoding : null; + var charstrings = this.getCharStrings(charset, encoding); // create the mapping between charstring and glyph id var glyphIds = []; @@ -3481,21 +3484,18 @@ var Type2CFF = (function Type2CFFClosure() { this.charstrings = charstrings; this.glyphIds = glyphIds; - - var data = []; - for (var i = 0, ii = bytes.length; i < ii; ++i) - data.push(bytes[i]); - return data; }, - - getCharStrings: function cff_charstrings(charsets, encoding, - privateDict, properties) { + getCharStrings: function getCharStrings(charsets, encoding) { var charstrings = []; var unicodeUsed = []; var unassignedUnicodeItems = []; var inverseEncoding = []; - for (var charcode in encoding) - inverseEncoding[encoding[charcode]] = charcode | 0; + // CID fonts don't have an encoding. + if (encoding !== null) + for (var charcode in encoding) + inverseEncoding[encoding[charcode]] = charcode | 0; + else + inverseEncoding = charsets; for (var i = 0, ii = charsets.length; i < ii; i++) { var glyph = charsets[i]; if (glyph == '.notdef') @@ -3531,284 +3531,80 @@ var Type2CFF = (function Type2CFFClosure() { } // sort the array by the unicode value (again) - charstrings.sort(function type2CFFGetCharStringsSort(a, b) { + charstrings.sort(function getCharStringsSort(a, b) { return a.unicode - b.unicode; }); return charstrings; - }, + } + }; - parseEncoding: function cff_parseencoding(pos, properties, strings, - charset) { - var encoding = {}; - var bytes = this.bytes; - var result = { - encoding: encoding, - hasSupplement: false - }; + return CFFFont; +})(); - function readSupplement() { - var supplementsCount = bytes[pos++]; - for (var i = 0; i < supplementsCount; i++) { - var code = bytes[pos++]; - var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); - encoding[code] = properties.differences.indexOf(strings[sid]); - } - } - - if (pos == 0 || pos == 1) { - var gid = 1; - var baseEncoding = pos ? Encodings.ExpertEncoding : - Encodings.StandardEncoding; - for (var i = 0, ii = charset.length; i < ii; i++) { - var index = baseEncoding.indexOf(charset[i]); - if (index != -1) - encoding[index] = gid++; +var CFFParser = (function CFFParserClosure() { + function CFFParser(file, properties) { + this.bytes = file.getBytes(); + this.properties = properties; + } + CFFParser.prototype = { + parse: function parse() { + var properties = this.properties; + var cff = new CFF(); + this.cff = cff; + + // The first five sections must be in order, all the others are reached + // via offsets contained in one of the below. + var header = this.parseHeader(); + var nameIndex = this.parseIndex(header.endPos); + var topDictIndex = this.parseIndex(nameIndex.endPos); + var stringIndex = this.parseIndex(topDictIndex.endPos); + var globalSubrIndex = this.parseIndex(stringIndex.endPos); + + var topDictParsed = this.parseDict(topDictIndex.obj.get(0)); + var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings); + + cff.header = header.obj; + cff.names = this.parseNameIndex(nameIndex.obj); + cff.strings = this.parseStringIndex(stringIndex.obj); + cff.topDict = topDict; + cff.globalSubrIndex = globalSubrIndex.obj; + + this.parsePrivateDict(cff.topDict); + + cff.isCIDFont = topDict.hasName('ROS'); + + var charStringOffset = topDict.getByName('CharStrings'); + cff.charStrings = this.parseCharStrings(charStringOffset); + + var charset, encoding; + if (cff.isCIDFont) { + var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj; + for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) { + var dictRaw = fdArrayIndex.get(i); + var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), + cff.strings); + this.parsePrivateDict(fontDict); + cff.fdArray.push(fontDict); } + // cid fonts don't have an encoding + encoding = null; + charset = this.parseCharsets(topDict.getByName('charset'), + cff.charStrings.count, cff.strings, true); + cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'), + cff.charStrings.count); } else { - var format = bytes[pos++]; - switch (format & 0x7f) { - case 0: - var glyphsCount = bytes[pos++]; - for (var i = 1; i <= glyphsCount; i++) - encoding[bytes[pos++]] = i; - break; - - case 1: - var rangesCount = bytes[pos++]; - var gid = 1; - for (var i = 0; i < rangesCount; i++) { - var start = bytes[pos++]; - var left = bytes[pos++]; - for (var j = start; j <= start + left; j++) - encoding[j] = gid++; - } - break; - - default: - error('Unknow encoding format: ' + format + ' in CFF'); - break; - } - if (format & 0x80) { - readSupplement(); - result.hasSupplement = true; - } + charset = this.parseCharsets(topDict.getByName('charset'), + cff.charStrings.count, cff.strings, false); + encoding = this.parseEncoding(topDict.getByName('Encoding'), + properties, + cff.strings, charset.charset); } - return result; + cff.charset = charset; + cff.encoding = encoding; + + return cff; }, - - parseCharsets: function cff_parsecharsets(pos, length, strings) { - if (pos == 0) { - return ISOAdobeCharset.slice(); - } else if (pos == 1) { - return ExpertCharset.slice(); - } else if (pos == 2) { - return ExpertSubsetCharset.slice(); - } - - var bytes = this.bytes; - var format = bytes[pos++]; - var charset = ['.notdef']; - - // subtract 1 for the .notdef glyph - length -= 1; - - switch (format) { - case 0: - for (var i = 0; i < length; i++) { - var sid = (bytes[pos++] << 8) | bytes[pos++]; - charset.push(strings[sid]); - } - break; - case 1: - while (charset.length <= length) { - var sid = (bytes[pos++] << 8) | bytes[pos++]; - var count = bytes[pos++]; - for (var i = 0; i <= count; i++) - charset.push(strings[sid++]); - } - break; - case 2: - while (charset.length <= length) { - var sid = (bytes[pos++] << 8) | bytes[pos++]; - var count = (bytes[pos++] << 8) | bytes[pos++]; - for (var i = 0; i <= count; i++) - charset.push(strings[sid++]); - } - break; - default: - error('Unknown charset format'); - } - return charset; - }, - - parseCidMap: function cff_parsecharsets(pos, length) { - var bytes = this.bytes; - var format = bytes[pos++]; - - var encoding = {}; - var map = {encoding: encoding}; - - encoding[0] = 0; - - var gid = 1; - switch (format) { - case 0: - while (gid < length) { - var cid = (bytes[pos++] << 8) | bytes[pos++]; - encoding[cid] = gid++; - } - break; - case 1: - while (gid < length) { - var cid = (bytes[pos++] << 8) | bytes[pos++]; - var count = bytes[pos++]; - for (var i = 0; i <= count; i++) - encoding[cid++] = gid++; - } - break; - case 2: - while (gid < length) { - var cid = (bytes[pos++] << 8) | bytes[pos++]; - var count = (bytes[pos++] << 8) | bytes[pos++]; - for (var i = 0; i <= count; i++) - encoding[cid++] = gid++; - } - break; - default: - error('Unknown charset format'); - } - return map; - }, - - getPrivDict: function cff_getprivdict(baseDict, strings) { - var dict = {}; - - // default values - dict['defaultWidthX'] = 0; - dict['nominalWidthX'] = 0; - - for (var i = 0, ii = baseDict.length; i < ii; ++i) { - var pair = baseDict[i]; - var key = pair[0]; - var value = pair[1]; - switch (key) { - case 20: - dict['defaultWidthX'] = value[0]; - case 21: - dict['nominalWidthX'] = value[0]; - default: - TODO('interpret top dict key: ' + key); - } - } - return dict; - }, - getTopDict: function cff_gettopdict(baseDict, strings) { - var dict = {}; - - // default values - dict['Encoding'] = 0; - dict['charset'] = 0; - - for (var i = 0, ii = baseDict.length; i < ii; ++i) { - var pair = baseDict[i]; - var key = pair[0]; - var value = pair[1]; - switch (key) { - case 1: - dict['Notice'] = strings[value[0]]; - break; - case 4: - dict['Weight'] = strings[value[0]]; - break; - case 3094: - dict['BaseFontName'] = strings[value[0]]; - break; - case 5: - dict['FontBBox'] = value; - break; - case 13: - dict['UniqueID'] = value[0]; - break; - case 15: - dict['charset'] = value[0]; - break; - case 16: - dict['Encoding'] = value[0]; - break; - case 17: - dict['CharStrings'] = value[0]; - break; - case 18: - dict['Private'] = value; - break; - case 3102: - case 3103: - case 3104: - case 3105: - case 3106: - case 3107: - case 3108: - case 3109: - case 3110: - dict['cidOperatorPresent'] = true; - break; - default: - TODO('interpret top dict key: ' + key); - } - } - return dict; - }, - sanitizeName: function cff_sanitizeName(nameIndex) { - // There should really only be one font, but loop to make sure. - for (var i = 0, ii = nameIndex.length; i < ii; ++i) { - var data = nameIndex.get(i).data; - var length = data.length; - if (length > 127) - warn('Font had name longer than 127 chars, will be rejected.'); - // Only certain chars are permitted in the font name. - for (var j = 0; j < length; ++j) { - var c = data[j]; - if (j === 0 && c === 0) - continue; - if (c < 33 || c > 126) { - data[j] = 95; - continue; - } - switch (c) { - case 91: // [ - case 93: // ] - case 40: // ( - case 41: // ) - case 123: // { - case 125: // } - case 60: // < - case 62: // > - case 47: // / - case 37: // % - data[j] = 95; - break; - } - } - } - }, - getStrings: function cff_getStrings(stringIndex) { - function bytesToString(bytesArray) { - var str = ''; - for (var i = 0, ii = bytesArray.length; i < ii; i++) - str += String.fromCharCode(bytesArray[i]); - return str; - } - - var stringArray = []; - for (var i = 0, ii = CFFStrings.length; i < ii; i++) - stringArray.push(CFFStrings[i]); - - for (var i = 0, ii = stringIndex.length; i < ii; i++) - stringArray.push(bytesToString(stringIndex.get(i).data)); - - return stringArray; - }, - parseHeader: function cff_parseHeader() { + parseHeader: function parseHeader() { var bytes = this.bytes; var offset = 0; @@ -3816,17 +3612,18 @@ var Type2CFF = (function Type2CFFClosure() { ++offset; if (offset != 0) { - warning('cff data is shifted'); + warn('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; } - - return { - endPos: bytes[2], - offsetSize: bytes[3] - }; + var major = bytes[0]; + var minor = bytes[1]; + var hdrSize = bytes[2]; + var offSize = bytes[3]; + var header = new CFFHeader(major, minor, hdrSize, offSize); + return {obj: header, endPos: hdrSize}; }, - parseDict: function cff_parseDict(dict) { + parseDict: function parseDict(dict) { var pos = 0; function parseOperand() { @@ -3843,11 +3640,11 @@ var Type2CFF = (function Type2CFFClosure() { value = (value << 8) | dict[pos++]; value = (value << 8) | dict[pos++]; return value; - } else if (value <= 246) { + } else if (value >= 32 && value <= 246) { return value - 139; - } else if (value <= 250) { + } else if (value >= 247 && value <= 250) { return ((value - 247) * 256) + dict[pos++] + 108; - } else if (value <= 254) { + } else if (value >= 251 && value <= 254) { return -((value - 251) * 256) - dict[pos++] - 108; } else { error('255 is not a valid DICT command'); @@ -3885,27 +3682,8 @@ var Type2CFF = (function Type2CFFClosure() { while (pos < end) { var b = dict[pos]; if (b <= 21) { - if (b === 12) { - ++pos; - var op = dict[pos]; - if ((op > 14 && op < 17) || - (op > 23 && op < 30) || op > 38) { - warn('Invalid CFF dictionary key: ' + op); - // trying to replace it with initialRandomSeed - // to pass sanitizer - dict[pos] = 19; - } - var b = (b << 8) | op; - } - if (!operands.length && b == 8 && - dict[pos + 1] == 9) { - // no operands for FamilyBlues, removing the key - // and next one is FamilyOtherBlues - skipping them - // also replacing FamilyBlues to pass sanitizer - dict[pos] = 139; - pos += 2; - continue; - } + if (b === 12) + b = (b << 8) | dict[++pos]; entries.push([b, operands]); operands = []; ++pos; @@ -3915,10 +3693,12 @@ var Type2CFF = (function Type2CFFClosure() { } return entries; }, - parseIndex: function cff_parseIndex(pos) { + parseIndex: function parseIndex(pos) { + var cffIndex = new CFFIndex(); var bytes = this.bytes; - var count = bytes[pos++] << 8 | bytes[pos++]; + var count = (bytes[pos++] << 8) | bytes[pos++]; var offsets = []; + var start = pos; var end = pos; if (count != 0) { @@ -3936,26 +3716,938 @@ var Type2CFF = (function Type2CFFClosure() { } end = offsets[count]; } + for (var i = 0, ii = offsets.length - 1; i < ii; ++i) { + var offsetStart = offsets[i]; + var offsetEnd = offsets[i + 1]; + cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); + } + return {obj: cffIndex, endPos: end}; + }, + parseNameIndex: function parseNameIndex(index) { + var names = []; + for (var i = 0, ii = index.count; i < ii; ++i) { + var name = index.get(i); + // OTS doesn't allow names to be over 127 characters. + var length = Math.min(name.length, 127); + var data = []; + // OTS also only permits certain characters in the name. + for (var j = 0; j < length; ++j) { + var c = name[j]; + if (j === 0 && c === 0) { + data[j] = c; + continue; + } + if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ || + c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ || + c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ || + c === 47 /* / */ || c === 37 /* % */) { + data[j] = 95; + continue; + } + data[j] = c; + } + names.push(String.fromCharCode.apply(null, data)); + } + return names; + }, + parseStringIndex: function parseStringIndex(index) { + var strings = new CFFStrings(); + for (var i = 0, ii = index.count; i < ii; ++i) { + var data = index.get(i); + strings.add(String.fromCharCode.apply(null, data)); + } + return strings; + }, + createDict: function createDict(type, dict, strings) { + var cffDict = new type(strings); + var types = cffDict.types; - return { - get: function index_get(index) { - if (index >= count) - return null; + for (var i = 0, ii = dict.length; i < ii; ++i) { + var pair = dict[i]; + var key = pair[0]; + var value = pair[1]; + cffDict.setByKey(key, value); + } + return cffDict; + }, + parseCharStrings: function parseCharStrings(charStringOffset) { + var charStrings = this.parseIndex(charStringOffset).obj; + // The CFF specification state that the 'dotsection' command + // (12, 0) is deprecated and treated as a no-op, but all Type2 + // charstrings processors should support them. Unfortunately + // the font sanitizer don't. As a workaround the sequence (12, 0) + // is replaced by a useless (0, hmoveto). + var count = charStrings.count; + for (var i = 0; i < count; i++) { + var charstring = charStrings.get(i); - var start = offsets[index]; - var end = offsets[index + 1]; - return { - start: start, - end: end, - data: bytes.subarray(start, end) - }; - }, - length: count, - endPos: end - }; + var data = charstring; + var length = data.length; + for (var j = 0; j <= length; j) { + var value = data[j++]; + if (value == 12 && data[j++] == 0) { + data[j - 2] = 139; + data[j - 1] = 22; + } else if (value === 28) { + j += 2; + } else if (value >= 247 && value <= 254) { + j++; + } else if (value == 255) { + j += 4; + } + } + } + return charStrings; + }, + parsePrivateDict: function parsePrivateDict(parentDict) { + // no private dict, do nothing + if (!parentDict.hasName('Private')) + return; + var privateOffset = parentDict.getByName('Private'); + // make sure the params are formatted correctly + if (!isArray(privateOffset) || privateOffset.length !== 2) { + parentDict.removeByName('Private'); + return; + } + var size = privateOffset[0]; + var offset = privateOffset[1]; + // remove empty dicts or ones that refer to invalid location + if (size === 0 || offset >= this.bytes.length) { + parentDict.removeByName('Private'); + return; + } + + var privateDictEnd = offset + size; + var dictData = this.bytes.subarray(offset, privateDictEnd); + var dict = this.parseDict(dictData); + var privateDict = this.createDict(CFFPrivateDict, dict, + parentDict.strings); + parentDict.privateDict = privateDict; + + // Parse the Subrs index also since it's relative to the private dict. + if (!privateDict.getByName('Subrs')) + return; + var subrsOffset = privateDict.getByName('Subrs'); + var relativeOffset = offset + subrsOffset; + // Validate the offset. + if (subrsOffset === 0 || relativeOffset >= this.bytes.length) { + privateDict.removeByName('Subrs'); + return; + } + var subrsIndex = this.parseIndex(relativeOffset); + privateDict.subrsIndex = subrsIndex.obj; + }, + parseCharsets: function parsecharsets(pos, length, strings, cid) { + if (pos == 0) { + return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, + ISOAdobeCharset); + } else if (pos == 1) { + return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, + ExpertCharset); + } else if (pos == 2) { + return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, + ExpertSubsetCharset); + } + + var bytes = this.bytes; + var start = pos; + var format = bytes[pos++]; + var charset = ['.notdef']; + + // subtract 1 for the .notdef glyph + length -= 1; + + switch (format) { + case 0: + for (var i = 0; i < length; i++) { + var id = (bytes[pos++] << 8) | bytes[pos++]; + charset.push(cid ? id : strings.get(id)); + } + break; + case 1: + while (charset.length <= length) { + var id = (bytes[pos++] << 8) | bytes[pos++]; + var count = bytes[pos++]; + for (var i = 0; i <= count; i++) + charset.push(cid ? id++ : strings.get(id++)); + } + break; + case 2: + while (charset.length <= length) { + var id = (bytes[pos++] << 8) | bytes[pos++]; + var count = (bytes[pos++] << 8) | bytes[pos++]; + for (var i = 0; i <= count; i++) + charset.push(cid ? id++ : strings.get(id++)); + } + break; + default: + error('Unknown charset format'); + } + // Raw won't be needed if we actually compile the charset. + var end = pos; + var raw = bytes.subarray(start, end); + + return new CFFCharset(false, format, charset, raw); + }, + parseEncoding: function parseEncoding(pos, properties, strings, charset) { + var encoding = {}; + var bytes = this.bytes; + var predefined = false; + var hasSupplement = false; + var format; + var raw = null; + + function readSupplement() { + var supplementsCount = bytes[pos++]; + for (var i = 0; i < supplementsCount; i++) { + var code = bytes[pos++]; + var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); + encoding[code] = properties.differences.indexOf(strings.get(sid)); + } + } + + if (pos == 0 || pos == 1) { + predefined = true; + format = pos; + var gid = 1; + var baseEncoding = pos ? Encodings.ExpertEncoding : + Encodings.StandardEncoding; + for (var i = 0, ii = charset.length; i < ii; i++) { + var index = baseEncoding.indexOf(charset[i]); + if (index != -1) + encoding[index] = gid++; + } + } else { + var dataStart = pos; + var format = bytes[pos++]; + switch (format & 0x7f) { + case 0: + var glyphsCount = bytes[pos++]; + for (var i = 1; i <= glyphsCount; i++) + encoding[bytes[pos++]] = i; + break; + + case 1: + var rangesCount = bytes[pos++]; + var gid = 1; + for (var i = 0; i < rangesCount; i++) { + var start = bytes[pos++]; + var left = bytes[pos++]; + for (var j = start; j <= start + left; j++) + encoding[j] = gid++; + } + break; + + default: + error('Unknow encoding format: ' + format + ' in CFF'); + break; + } + var dataEnd = pos; + if (format & 0x80) { + // The font sanitizer does not support CFF encoding with a + // supplement, since the encoding is not really used to map + // between gid to glyph, let's overwrite what is declared in + // the top dictionary to let the sanitizer think the font use + // StandardEncoding, that's a lie but that's ok. + bytes[dataStart] &= 0x7f; + readSupplement(); + hasSupplement = true; + } + raw = bytes.subarray(dataStart, dataEnd); + } + format = format & 0x7f; + return new CFFEncoding(predefined, format, encoding, raw); + }, + parseFDSelect: function parseFDSelect(pos, length) { + var start = pos; + var bytes = this.bytes; + var format = bytes[pos++]; + var fdSelect = []; + switch (format) { + case 0: + for (var i = 0; i < length; ++i) { + var id = bytes[pos++]; + fdSelect.push(id); + } + break; + case 3: + var rangesCount = (bytes[pos++] << 8) | bytes[pos++]; + for (var i = 0; i < rangesCount; ++i) { + var first = (bytes[pos++] << 8) | bytes[pos++]; + var fdIndex = bytes[pos++]; + var next = (bytes[pos] << 8) | bytes[pos + 1]; + for (var j = first; j < next; ++j) + fdSelect.push(fdIndex); + } + // Advance past the sentinel(next). + pos += 2; + break; + default: + error('Unknown fdselect format ' + format); + break; + } + var end = pos; + return new CFFFDSelect(fdSelect, bytes.subarray(start, end)); } }; - - return Type2CFF; + return CFFParser; +})(); + +// Compact Font Format +var CFF = (function CFFClosure() { + function CFF() { + this.header = null; + this.names = []; + this.topDict = null; + this.strings = new CFFStrings(); + this.globalSubrIndex = null; + + // The following could really be per font, but since we only have one font + // store them here. + this.encoding = null; + this.charset = null; + this.charStrings = null; + this.fdArray = []; + this.fdSelect = null; + + this.isCIDFont = false; + } + return CFF; +})(); + +var CFFHeader = (function CFFHeaderClosure() { + function CFFHeader(major, minor, hdrSize, offSize) { + this.major = major; + this.minor = minor; + this.hdrSize = hdrSize; + this.offSize = offSize; + } + return CFFHeader; +})(); + +var CFFStrings = (function CFFStringsClosure() { + function CFFStrings() { + this.strings = []; + } + CFFStrings.prototype = { + get: function get(index) { + if (index >= 0 && index <= 390) + return CFFStandardStrings[index]; + if (index - 391 <= this.strings.length) + return this.strings[index - 391]; + return CFFStandardStrings[0]; + }, + add: function add(value) { + this.strings.push(value); + }, + get count() { + return this.strings.length; + } + }; + return CFFStrings; +})(); + +var CFFIndex = (function CFFIndexClosure() { + function CFFIndex() { + this.objects = []; + this.length = 0; + } + CFFIndex.prototype = { + add: function add(data) { + this.length += data.length; + this.objects.push(data); + }, + get: function get(index) { + return this.objects[index]; + }, + get count() { + return this.objects.length; + } + }; + return CFFIndex; +})(); + +var CFFDict = (function CFFDictClosure() { + function CFFDict(tables, strings) { + this.keyToNameMap = tables.keyToNameMap; + this.nameToKeyMap = tables.nameToKeyMap; + this.defaults = tables.defaults; + this.types = tables.types; + this.opcodes = tables.opcodes; + this.order = tables.order; + this.strings = strings; + this.values = {}; + } + CFFDict.prototype = { + // value should always be an array + setByKey: function setByKey(key, value) { + if (!(key in this.keyToNameMap)) + return false; + // ignore empty values + if (value.length === 0) + return true; + var type = this.types[key]; + // remove the array wrapping these types of values + if (type === 'num' || type === 'sid' || type === 'offset') + value = value[0]; + this.values[key] = value; + return true; + }, + hasName: function hasName(name) { + return this.nameToKeyMap[name] in this.values; + }, + getByName: function getByName(name) { + if (!(name in this.nameToKeyMap)) + error('Invalid dictionary name "' + name + '"'); + var key = this.nameToKeyMap[name]; + if (!(key in this.values)) + return this.defaults[key]; + return this.values[key]; + }, + removeByName: function removeByName(name) { + delete this.values[this.nameToKeyMap[name]]; + } + }; + CFFDict.createTables = function createTables(layout) { + var tables = { + keyToNameMap: {}, + nameToKeyMap: {}, + defaults: {}, + types: {}, + opcodes: {}, + order: [] + }; + for (var i = 0, ii = layout.length; i < ii; ++i) { + var entry = layout[i]; + var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0]; + tables.keyToNameMap[key] = entry[1]; + tables.nameToKeyMap[entry[1]] = key; + tables.types[key] = entry[2]; + tables.defaults[key] = entry[3]; + tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]]; + tables.order.push(key); + } + return tables; + }; + return CFFDict; +})(); + +var CFFTopDict = (function CFFTopDictClosure() { + var layout = [ + [[12, 30], 'ROS', ['sid', 'sid', 'num'], null], + [[12, 20], 'SyntheticBase', 'num', null], + [0, 'version', 'sid', null], + [1, 'Notice', 'sid', null], + [[12, 0], 'Copyright', 'sid', null], + [2, 'FullName', 'sid', null], + [3, 'FamilyName', 'sid', null], + [4, 'Weight', 'sid', null], + [[12, 1], 'isFixedPitch', 'num', 0], + [[12, 2], 'ItalicAngle', 'num', 0], + [[12, 3], 'UnderlinePosition', 'num', -100], + [[12, 4], 'UnderlineThickness', 'num', 50], + [[12, 5], 'PaintType', 'num', 0], + [[12, 6], 'CharstringType', 'num', 2], + [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'], + [.001, 0, 0, .001, 0, 0]], + [13, 'UniqueID', 'num', null], + [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]], + [[12, 8], 'StrokeWidth', 'num', 0], + [14, 'XUID', 'array', null], + [15, 'charset', 'offset', 0], + [16, 'Encoding', 'offset', 0], + [17, 'CharStrings', 'offset', 0], + [18, 'Private', ['offset', 'offset'], null], + [[12, 21], 'PostScript', 'sid', null], + [[12, 22], 'BaseFontName', 'sid', null], + [[12, 23], 'BaseFontBlend', 'delta', null], + [[12, 31], 'CIDFontVersion', 'num', 0], + [[12, 32], 'CIDFontRevision', 'num', 0], + [[12, 33], 'CIDFontType', 'num', 0], + [[12, 34], 'CIDCount', 'num', 8720], + [[12, 35], 'UIDBase', 'num', null], + [[12, 36], 'FDArray', 'offset', null], + [[12, 37], 'FDSelect', 'offset', null], + [[12, 38], 'FontName', 'sid', null]]; + var tables = null; + function CFFTopDict(strings) { + if (tables === null) + tables = CFFDict.createTables(layout); + CFFDict.call(this, tables, strings); + this.privateDict = null; + } + CFFTopDict.prototype = Object.create(CFFDict.prototype); + return CFFTopDict; +})(); + +var CFFPrivateDict = (function CFFPrivateDictClosure() { + var layout = [ + [6, 'BlueValues', 'delta', null], + [7, 'OtherBlues', 'delta', null], + [8, 'FamilyBlues', 'delta', null], + [9, 'FamilyOtherBlues', 'delta', null], + [[12, 9], 'BlueScale', 'num', 0.039625], + [[12, 10], 'BlueShift', 'num', 7], + [[12, 11], 'BlueFuzz', 'num', 1], + [10, 'StdHW', 'num', null], + [11, 'StdVW', 'num', null], + [[12, 12], 'StemSnapH', 'delta', null], + [[12, 13], 'StemSnapV', 'delta', null], + [[12, 14], 'ForceBold', 'num', 0], + [[12, 17], 'LanguageGroup', 'num', 0], + [[12, 18], 'ExpansionFactor', 'num', 0.06], + [[12, 19], 'initialRandomSeed', 'num', 0], + [19, 'Subrs', 'offset', null], + [20, 'defaultWidthX', 'num', 0], + [21, 'nominalWidthX', 'num', 0] + ]; + var tables = null; + function CFFPrivateDict(strings) { + if (tables === null) + tables = CFFDict.createTables(layout); + CFFDict.call(this, tables, strings); + this.subrsIndex = null; + } + CFFPrivateDict.prototype = Object.create(CFFDict.prototype); + return CFFPrivateDict; +})(); + +var CFFCharsetPredefinedTypes = { + ISO_ADOBE: 0, + EXPERT: 1, + EXPERT_SUBSET: 2 +}; +var CFFCharsetEmbeddedTypes = { + FORMAT0: 0, + FORMAT1: 1, + FORMAT2: 2 +}; +var CFFCharset = (function CFFCharsetClosure() { + function CFFCharset(predefined, format, charset, raw) { + this.predefined = predefined; + this.format = format; + this.charset = charset; + this.raw = raw; + } + return CFFCharset; +})(); + +var CFFEncodingPredefinedTypes = { + STANDARD: 0, + EXPERT: 1 +}; +var CFFCharsetEmbeddedTypes = { + FORMAT0: 0, + FORMAT1: 1 +}; +var CFFEncoding = (function CFFEncodingClosure() { + function CFFEncoding(predefined, format, encoding, raw) { + this.predefined = predefined; + this.format = format; + this.encoding = encoding; + this.raw = raw; + } + return CFFEncoding; +})(); + +var CFFFDSelect = (function CFFFDSelectClosure() { + function CFFFDSelect(fdSelect, raw) { + this.fdSelect = fdSelect; + this.raw = raw; + } + return CFFFDSelect; +})(); + +// Helper class to keep track of where an offset is within the data and helps +// filling in that offset once it's known. +var CFFOffsetTracker = (function CFFOffsetTrackerClosure() { + function CFFOffsetTracker() { + this.offsets = {}; + } + CFFOffsetTracker.prototype = { + isTracking: function isTracking(key) { + return key in this.offsets; + }, + track: function track(key, location) { + if (key in this.offsets) + error('Already tracking location of ' + key); + this.offsets[key] = location; + }, + offset: function offset(value) { + for (var key in this.offsets) { + this.offsets[key] += value; + } + }, + setEntryLocation: function setEntryLocation(key, values, output) { + if (!(key in this.offsets)) + error('Not tracking location of ' + key); + var data = output.data; + var dataOffset = this.offsets[key]; + var size = 5; + for (var i = 0, ii = values.length; i < ii; ++i) { + var offset0 = i * size + dataOffset; + var offset1 = offset0 + 1; + var offset2 = offset0 + 2; + var offset3 = offset0 + 3; + var offset4 = offset0 + 4; + // It's easy to screw up offsets so perform this sanity check. + if (data[offset0] !== 0x1d || data[offset1] !== 0 || + data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) + error('writing to an offset that is not empty'); + var value = values[i]; + data[offset0] = 0x1d; + data[offset1] = (value >> 24) & 0xFF; + data[offset2] = (value >> 16) & 0xFF; + data[offset3] = (value >> 8) & 0xFF; + data[offset4] = value & 0xFF; + } + } + }; + return CFFOffsetTracker; +})(); + +// Takes a CFF and converts it to the binary representation. +var CFFCompiler = (function CFFCompilerClosure() { + function stringToArray(str) { + var array = []; + for (var i = 0, ii = str.length; i < ii; ++i) + array[i] = str.charCodeAt(i); + + return array; + }; + function CFFCompiler(cff) { + this.cff = cff; + } + CFFCompiler.prototype = { + compile: function compile() { + var cff = this.cff; + var output = { + data: [], + length: 0, + add: function add(data) { + this.data = this.data.concat(data); + this.length = this.data.length; + } + }; + + // Compile the five entries that must be in order. + var header = this.compileHeader(cff.header); + output.add(header); + + var nameIndex = this.compileNameIndex(cff.names); + output.add(nameIndex); + + var compiled = this.compileTopDicts([cff.topDict], output.length); + output.add(compiled.output); + var topDictTracker = compiled.trackers[0]; + + var stringIndex = this.compileStringIndex(cff.strings.strings); + output.add(stringIndex); + + var globalSubrIndex = this.compileIndex(cff.globalSubrIndex); + output.add(globalSubrIndex); + + // Now start on the other entries that have no specfic order. + if (cff.encoding && cff.topDict.hasName('Encoding')) { + if (cff.encoding.predefined) { + topDictTracker.setEntryLocation('Encoding', [cff.encoding.format], + output); + } else { + var encoding = this.compileEncoding(cff.encoding); + topDictTracker.setEntryLocation('Encoding', [output.length], output); + output.add(encoding); + } + } + + if (cff.charset && cff.topDict.hasName('charset')) { + if (cff.charset.predefined) { + topDictTracker.setEntryLocation('charset', [cff.charset.format], + output); + } else { + var charset = this.compileCharset(cff.charset); + topDictTracker.setEntryLocation('charset', [output.length], output); + output.add(charset); + } + } + + var charStrings = this.compileCharStrings(cff.charStrings); + topDictTracker.setEntryLocation('CharStrings', [output.length], output); + output.add(charStrings); + + if (cff.isCIDFont) { + var compiled = this.compileTopDicts(cff.fdArray, output.length); + topDictTracker.setEntryLocation('FDArray', [output.length], output); + output.add(compiled.output); + var fontDictTrackers = compiled.trackers; + + this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output); + + topDictTracker.setEntryLocation('FDSelect', [output.length], output); + var fdSelect = this.compileFDSelect(cff.fdSelect.raw); + output.add(fdSelect); + } + + this.compilePrivateDicts([cff.topDict], [topDictTracker], output); + + return output.data; + }, + encodeNumber: function encodeNumber(value) { + if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt + return this.encodeInteger(value); + else + return this.encodeFloat(value); + }, + encodeFloat: function encodeFloat(value) { + value = value.toString(); + // Strip off the any leading zeros. + if (value.substr(0, 2) === '0.') + value = value.substr(1); + else if (value.substr(0, 3) === '-0.') + value = '-' + value.substr(2); + var nibbles = []; + for (var i = 0, ii = value.length; i < ii; ++i) { + var a = value.charAt(i), b = value.charAt(i + 1); + var nibble; + if (a === 'e' && b === '-') { + nibble = 0xc; + ++i; + } else if (a === '.') { + nibble = 0xa; + } else if (a === 'E') { + nibble = 0xb; + } else if (a === '-') { + nibble = 0xe; + } else { + nibble = a; + } + nibbles.push(nibble); + } + nibbles.push(0xf); + if (nibbles.length % 2) + nibbles.push(0xf); + var out = [30]; + for (var i = 0, ii = nibbles.length; i < ii; i += 2) + out.push(nibbles[i] << 4 | nibbles[i + 1]); + return out; + }, + encodeInteger: function encodeInteger(value) { + var code; + if (value >= -107 && value <= 107) { + code = [value + 139]; + } else if (value >= 108 && value <= 1131) { + value = [value - 108]; + code = [(value >> 8) + 247, value & 0xFF]; + } else if (value >= -1131 && value <= -108) { + value = -value - 108; + code = [(value >> 8) + 251, value & 0xFF]; + } else if (value >= -32768 && value <= 32767) { + code = [0x1c, (value >> 8) & 0xFF, value & 0xFF]; + } else { + code = [0x1d, + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF]; + } + return code; + }, + compileHeader: function compileHeader(header) { + return [ + header.major, + header.minor, + header.hdrSize, + header.offSize + ]; + }, + compileNameIndex: function compileNameIndex(names) { + var nameIndex = new CFFIndex(); + for (var i = 0, ii = names.length; i < ii; ++i) + nameIndex.add(stringToArray(names[i])); + return this.compileIndex(nameIndex); + }, + compileTopDicts: function compileTopDicts(dicts, length) { + var fontDictTrackers = []; + var fdArrayIndex = new CFFIndex(); + for (var i = 0, ii = dicts.length; i < ii; ++i) { + var fontDict = dicts[i]; + var fontDictTracker = new CFFOffsetTracker(); + var fontDictData = this.compileDict(fontDict, fontDictTracker); + fontDictTrackers.push(fontDictTracker); + fdArrayIndex.add(fontDictData); + fontDictTracker.offset(length); + } + fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers); + return { + trackers: fontDictTrackers, + output: fdArrayIndex + }; + }, + compilePrivateDicts: function compilePrivateDicts(dicts, trackers, output) { + for (var i = 0, ii = dicts.length; i < ii; ++i) { + var fontDict = dicts[i]; + if (!fontDict.privateDict || !fontDict.hasName('Private')) + continue; + var privateDict = fontDict.privateDict; + var privateDictTracker = new CFFOffsetTracker(); + var privateDictData = this.compileDict(privateDict, privateDictTracker); + + privateDictTracker.offset(output.length); + trackers[i].setEntryLocation('Private', + [privateDictData.length, output.length], + output); + output.add(privateDictData); + + if (privateDict.subrsIndex && privateDict.hasName('Subrs')) { + var subrs = this.compileIndex(privateDict.subrsIndex); + privateDictTracker.setEntryLocation('Subrs', [privateDictData.length], + output); + output.add(subrs); + } + } + }, + compileDict: function compileDict(dict, offsetTracker) { + var out = []; + // The dictionary keys must be in a certain order. + var order = dict.order; + for (var i = 0; i < order.length; ++i) { + var key = order[i]; + if (!(key in dict.values)) + continue; + var values = dict.values[key]; + var types = dict.types[key]; + if (!isArray(types)) types = [types]; + if (!isArray(values)) values = [values]; + + // Remove any empty dict values. + if (values.length === 0) + continue; + + for (var j = 0, jj = types.length; j < jj; ++j) { + var type = types[j]; + var value = values[j]; + switch (type) { + case 'num': + case 'sid': + out = out.concat(this.encodeNumber(value)); + break; + case 'offset': + // For offsets we just insert a 32bit integer so we don't have to + // deal with figuring out the length of the offset when it gets + // replaced later on by the compiler. + var name = dict.keyToNameMap[key]; + // Some offsets have the offset and the length, so just record the + // position of the first one. + if (!offsetTracker.isTracking(name)) + offsetTracker.track(name, out.length); + out = out.concat([0x1d, 0, 0, 0, 0]); + break; + case 'array': + case 'delta': + out = out.concat(this.encodeNumber(value)); + for (var k = 1, kk = values.length; k < kk; ++k) + out = out.concat(this.encodeNumber(values[k])); + break; + default: + error('Unknown data type of ' + type); + break; + } + } + out = out.concat(dict.opcodes[key]); + } + return out; + }, + compileStringIndex: function compileStringIndex(strings) { + var stringIndex = new CFFIndex(); + for (var i = 0, ii = strings.length; i < ii; ++i) + stringIndex.add(stringToArray(strings[i])); + return this.compileIndex(stringIndex); + }, + compileGlobalSubrIndex: function compileGlobalSubrIndex() { + var globalSubrIndex = this.cff.globalSubrIndex; + this.out.writeByteArray(this.compileIndex(globalSubrIndex)); + }, + compileCharStrings: function compileCharStrings(charStrings) { + return this.compileIndex(charStrings); + }, + compileCharset: function compileCharset(charset) { + return this.compileTypedArray(charset.raw); + }, + compileEncoding: function compileEncoding(encoding) { + return this.compileTypedArray(encoding.raw); + }, + compileFDSelect: function compileFDSelect(fdSelect) { + return this.compileTypedArray(fdSelect); + }, + compileTypedArray: function compileTypedArray(data) { + var out = []; + for (var i = 0, ii = data.length; i < ii; ++i) + out[i] = data[i]; + return out; + }, + compileIndex: function compileIndex(index, trackers) { + trackers = trackers || []; + var objects = index.objects; + // First 2 bytes contains the number of objects contained into this index + var count = objects.length; + + // If there is no object, just create an index. This technically + // should just be [0, 0] but OTS has an issue with that. + if (count == 0) + return [0, 0, 0]; + + var data = [(count >> 8) & 0xFF, count & 0xff]; + + var lastOffset = 1; + for (var i = 0; i < count; ++i) + lastOffset += objects[i].length; + + var offsetSize; + if (lastOffset < 0x100) + offsetSize = 1; + else if (lastOffset < 0x10000) + offsetSize = 2; + else if (lastOffset < 0x1000000) + offsetSize = 3; + else + offsetSize = 4; + + // Next byte contains the offset size use to reference object in the file + data.push(offsetSize); + + // Add another offset after this one because we need a new offset + var relativeOffset = 1; + for (var i = 0; i < count + 1; i++) { + if (offsetSize === 1) { + data.push(relativeOffset & 0xFF); + } else if (offsetSize === 2) { + data.push((relativeOffset >> 8) & 0xFF, + relativeOffset & 0xFF); + } else if (offsetSize === 3) { + data.push((relativeOffset >> 16) & 0xFF, + (relativeOffset >> 8) & 0xFF, + relativeOffset & 0xFF); + } else { + data.push((relativeOffset >>> 24) & 0xFF, + (relativeOffset >> 16) & 0xFF, + (relativeOffset >> 8) & 0xFF, + relativeOffset & 0xFF); + } + + if (objects[i]) + relativeOffset += objects[i].length; + } + var offset = data.length; + + for (var i = 0; i < count; i++) { + // Notify the tracker where the object will be offset in the data. + if (trackers[i]) + trackers[i].offset(data.length); + for (var j = 0, jj = objects[i].length; j < jj; j++) + data.push(objects[i][j]); + } + return data; + } + }; + return CFFCompiler; })(); diff --git a/src/function.js b/src/function.js index 4f81158f0..5ff5840c5 100644 --- a/src/function.js +++ b/src/function.js @@ -81,7 +81,7 @@ var PDFFunction = (function PDFFunctionClosure() { function toMultiArray(arr) { var inputLength = arr.length; var outputLength = arr.length / 2; - var out = new Array(outputLength); + var out = []; var index = 0; for (var i = 0; i < inputLength; i += 2) { out[index] = [arr[i], arr[i + 1]]; @@ -364,7 +364,7 @@ var PDFFunction = (function PDFFunctionClosure() { return cache.get(key); var stack = evaluator.execute(initialStack); - var transformed = new Array(numOutputs); + var transformed = []; for (i = numOutputs - 1; i >= 0; --i) { var out = stack.pop(); var rangeIndex = 2 * i; diff --git a/src/image.js b/src/image.js index 6e7ab2020..7c23a3426 100644 --- a/src/image.js +++ b/src/image.js @@ -365,7 +365,7 @@ var PDFImage = (function PDFImageClosure() { function loadJpegStream(id, imageData, objs) { var img = new Image(); - img.onload = (function jpegImageLoaderOnload() { + img.onload = (function loadJpegStream_onloadClosure() { objs.resolve(id, img); }); img.src = 'data:image/jpeg;base64,' + window.btoa(imageData); diff --git a/src/jpx.js b/src/jpx.js index b420b04e1..7a13da0b5 100644 --- a/src/jpx.js +++ b/src/jpx.js @@ -159,7 +159,7 @@ var JpxImage = (function JpxImageClosure() { })(); // Implements C.3. Arithmetic decoding procedures - var ArithmeticDecoder = (function arithmeticDecoderClosure() { + var ArithmeticDecoder = (function ArithmeticDecoderClosure() { var QeTable = [ {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, diff --git a/src/metadata.js b/src/metadata.js new file mode 100644 index 000000000..7f3f24a86 --- /dev/null +++ b/src/metadata.js @@ -0,0 +1,66 @@ +/* -*- 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 Metadata = PDFJS.Metadata = (function MetadataClosure() { + function Metadata(meta) { + if (typeof meta === 'string') { + var parser = new DOMParser(); + meta = parser.parseFromString(meta, 'application/xml'); + } else if (!(meta instanceof Document)) { + error('Metadata: Invalid metadata object'); + } + + this.metaDocument = meta; + this.metadata = {}; + this.parse(); + } + + Metadata.prototype = { + parse: function() { + var doc = this.metaDocument; + var rdf = doc.documentElement; + + if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in + rdf = rdf.firstChild; + while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') + rdf = rdf.nextSibling; + } + + var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null; + if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) + return; + + var childNodes = rdf.childNodes, desc, namespace, entries, entry; + + for (var i = 0, length = childNodes.length; i < length; i++) { + desc = childNodes[i]; + if (desc.nodeName.toLowerCase() !== 'rdf:description') + continue; + + entries = []; + for (var ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) { + if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') + entries.push(desc.childNodes[ii]); + } + + for (ii = 0, iLength = entries.length; ii < iLength; ii++) { + var entry = entries[ii]; + var name = entry.nodeName.toLowerCase(); + this.metadata[name] = entry.textContent.trim(); + } + } + }, + + get: function(name) { + return this.metadata[name] || null; + }, + + has: function(name) { + return typeof this.metadata[name] !== 'undefined'; + } + }; + + return Metadata; +})(); diff --git a/src/obj.js b/src/obj.js index 3c649fb06..2eb9c6f1d 100644 --- a/src/obj.js +++ b/src/obj.js @@ -111,6 +111,22 @@ var Catalog = (function CatalogClosure() { } Catalog.prototype = { + get metadata() { + var ref = this.catDict.get('Metadata'); + var stream = this.xref.fetchIfRef(ref); + var metadata; + if (stream && isDict(stream.dict)) { + var type = stream.dict.get('Type'); + var subtype = stream.dict.get('Subtype'); + + if (isName(type) && isName(subtype) && + type.name === 'Metadata' && subtype.name === 'XML') { + metadata = stringToPDFString(bytesToString(stream.getBytes())); + } + } + + return shadow(this, 'metadata', metadata); + }, get toplevelPagesDict() { var pagesObj = this.catDict.get('Pages'); assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference'); diff --git a/src/stream.js b/src/stream.js index f76a07b4c..d31f3d50b 100644 --- a/src/stream.js +++ b/src/stream.js @@ -2056,7 +2056,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (this.eoblock) { code = this.lookBits(7); p = twoDimTable[code]; - if (p[0] > 0) { + if (p && p[0] > 0) { this.eatBits(p[0]); return p[1]; } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index d371a7853..c8b008d71 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -25,6 +25,8 @@ !issue1249.pdf !smaskdim.pdf !type4psfunc.pdf +!issue1350.pdf !S2.pdf !zerowidthline.pdf +!issue1002.pdf !issue925.pdf diff --git a/test/pdfs/issue1002.pdf b/test/pdfs/issue1002.pdf new file mode 100644 index 000000000..b764c8841 Binary files /dev/null and b/test/pdfs/issue1002.pdf differ diff --git a/test/pdfs/issue1309.pdf.link b/test/pdfs/issue1309.pdf.link new file mode 100644 index 000000000..1351d45ef --- /dev/null +++ b/test/pdfs/issue1309.pdf.link @@ -0,0 +1 @@ +http://www.lufthansa.com/mediapool/pdf/31/media_907231.pdf diff --git a/test/pdfs/issue1317.pdf.link b/test/pdfs/issue1317.pdf.link new file mode 100644 index 000000000..67c4d50ef --- /dev/null +++ b/test/pdfs/issue1317.pdf.link @@ -0,0 +1 @@ +http://iliad.fr/presse/2012/CP_080312_Free_mobile.pdf diff --git a/test/pdfs/issue1350.pdf b/test/pdfs/issue1350.pdf new file mode 100644 index 000000000..8b5a06aab --- /dev/null +++ b/test/pdfs/issue1350.pdf @@ -0,0 +1,2184 @@ +%PDF-1.4 +% +1 0 obj +<>/Font<>>>/MediaBox[0 0 612 492]>> +endobj +2 0 obj +[/PDF/Text/ImageC] +endobj +4 0 obj +<>stream +0.000 0.000 0.000 rg +0.000 0.000 0.000 RG +q +1.000 0.000 0.000 RG + +q +561.600 0 0 254.991 25.200 501.009 cm +/I1 Do +Q + +BT +380.88 700.456 Td +/F2.0 13 Tf +<373238363438313231382d6d656d62> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 681.008 Td +/F2.0 14 Tf +<4d6574726f526f636b> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 661.502 Td +/F2.0 10 Tf +<5768617420796f7520676574> Tj +ET + + +BT +43.2 650.632 Td +/F1.0 10 Tf +<31206d6f6e7468206d656d626572736869702c20756e6c696d697465642072656e74616c7320616e6420626567696e6e65727320636c696d62696e6720636c617373> Tj +ET + + +BT +43.2 632.562 Td +/F2.0 10 Tf +<52656465656d61626c652061742074686520666f6c6c6f77696e67206c6f636174696f6e287329> Tj +ET + + +BT +43.2 621.692 Td +/F1.0 10 Tf +<3639204e6f726d616e2053747265657420457665726574742c204d41> Tj +ET + + +BT +43.2 596.422 Td +/F1.0 10 Tf +<20> Tj +ET + + +BT +381.6 665.92 Td +/F2.0 10 Tf +[<5075726368617365642062793a2053616d2054> 74.21875 <6f62696e2d686f63687374616474>] TJ +ET + +0.000 0.000 0.000 rg + +BT +381.6 575.92 Td +/F2.0 10 Tf +<497373756564206279204d6574726f526f636b> Tj +ET + + +BT +381.6 565.05 Td +/F2.0 10 Tf +[<497373756564206f6e20546875205365702032322031393a33303a33302055544320323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 554.18 Td +/F2.0 10 Tf +<4e6f742076616c696420756e74696c3a20467269205365702032332031393a33303a333020555443> Tj +ET + + +BT +381.6 543.31 Td +/F2.0 10 Tf +[<323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 532.44 Td +/F2.0 10 Tf +<50726f6d6f74696f6e616c2076616c756520657870697265733a20546875204d6172203232> Tj +ET + + +BT +381.6 521.57 Td +/F2.0 10 Tf +<30303a30303a3030202d3034303020323031322a> Tj +ET + +0.000 0.000 0.000 rg + +BT +36.0 488.064 Td +/F2.0 12 Tf +<4d6574726f526f636b20284d656d6229> Tj +ET + + +BT +36.0 467.92 Td +/F2.0 10 Tf +<496e737472756374696f6e73> Tj +ET + + +BT +36.0 449.85 Td +/F1.0 10 Tf +<2a20506c656173652063616c6c203631372d3338372d3736323520284576657265747429206f72203937382d3439392d37363235> Tj +ET + + +BT +36.0 438.98 Td +/F1.0 10 Tf +<284e657762757279706f72742920746f207265736572766520636c6173736573206f72206368616c6c656e676520636f757273652074696d652e> Tj +ET + + +BT +36.0 428.11 Td +/F1.0 10 Tf +[<466f722073687574746c6520736572766963652066726f6d207468652057> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702c20706c656173652063616c6c>] TJ +ET + + +BT +36.0 417.24 Td +/F1.0 10 Tf +<3631372d3338372d373632352075706f6e206172726976616c206174207468652073746174696f6e2e> Tj +ET + + +BT +36.0 406.37 Td +/F1.0 10 Tf +<2a205072696e7420766f756368657220616e64206272696e6720746f2061206d65726368616e74206c6f636174696f6e206c6973746564206f6e> Tj +ET + + +BT +36.0 395.5 Td +/F1.0 10 Tf +[<7468697320766f7563686572> 55.17578125 <2e>] TJ +ET + + +BT +36.0 384.63 Td +/F1.0 10 Tf +[<2a2057> 18.06640625 <7269746520796f7572206e616d6520616e642066756c6c206164647265737320666f756e64206f6e20796f757220494420696e20746865>] TJ +ET + + +BT +36.0 373.760000000001 Td +/F1.0 10 Tf +<73706163652070726f76696465642062656c6f77202874686520706572736f6e2072656465656d696e67207468697320766f7563686572> Tj +ET + + +BT +36.0 362.890000000001 Td +/F1.0 10 Tf +<73686f756c642070726f76696465207468697320696e666f726d6174696f6e2c206e6f74206e65636573736172696c7920746865> Tj +ET + + +BT +36.0 352.020000000001 Td +/F1.0 10 Tf +<70757263686173657220696620676976656e20617320612067696674292e20> Tj +ET + + +BT +36.0 341.150000000001 Td +/F1.0 10 Tf +<2a2050726573656e742076616c69642c20676f7665726e6d656e742d6973737565642070686f746f204944207768656e> Tj +ET + + +BT +36.0 330.280000000001 Td +/F1.0 10 Tf +<72656465656d696e6720796f757220766f75636865727320286e616d65206f6e20494420646f6573206e6f74206861766520746f> Tj +ET + + +BT +36.0 319.410000000001 Td +/F1.0 10 Tf +<6d6174636820746865207075726368617365722773206e616d6520696620676976656e20617320612067696674292e> Tj +ET + + +BT +36.0 308.540000000001 Td +ET + + +BT +36.0 297.670000000001 Td +/F1.0 10 Tf +[<496e737472756374696f6e7320666f72206d65726368616e743a2042757957> -0.0 <6974684d652068617320616c726561647920636f6c6c6563746564>] TJ +ET + + +BT +36.0 286.800000000001 Td +/F1.0 10 Tf +<7061796d656e74206f6e20796f757220626568616c662e> Tj +ET + + +BT +306.0 467.92 Td +/F2.0 10 Tf +<44657461696c73> Tj +ET + + +BT +306.0 449.85 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 438.98 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 428.11 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 417.24 Td +/F1.0 10 Tf +<6d656d6265727368697020626567696e732066726f6d20796f75722066697273742076697369743b20706c656173652070726573656e74> Tj +ET + + +BT +306.0 406.37 Td +/F1.0 10 Tf +<766f756368657220746f2061637469766174652e20> Tj +ET + + +BT +306.0 395.5 Td +/F1.0 10 Tf +[ -37.109375 18.06640625 <7320636c6173732e20> 55.17578125 <41> 55.17578125 <20636c617373206973206e6f74>] TJ +ET + + +BT +306.0 384.63 Td +/F1.0 10 Tf +[<6e656365737361727920696620796f7520616c7265616479206b6e6f7720686f7720746f2062656c6179> 74.21875 <2e>] TJ +ET + + +BT +306.0 373.760000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 362.890000000001 Td +/F1.0 10 Tf +<70756e636820636172642e> Tj +ET + + +BT +306.0 352.020000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 341.150000000001 Td +/F1.0 10 Tf +<6c6f636174696f6e206f6e6c793b206d757374206265206174206c6561737420342720362220746f20676f207468726f7567682074686520636f757273652e> Tj +ET + + +BT +306.0 330.280000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 319.410000000001 Td +/F1.0 10 Tf +[<617661696c6162696c697479> 74.21875 <2e>] TJ +ET + + +BT +306.0 308.540000000001 Td +/F1.0 10 Tf +[ 55.17578125 <4167657320313820616e6420756e646572206d7573742068617665206120776169766572207369676e6564206279206120706172656e74>] TJ +ET + + +BT +306.0 297.670000000001 Td +/F1.0 10 Tf +<6f72206c6567616c20677561726469616e2e> Tj +ET + + +BT +306.0 286.800000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 275.930000000001 Td +/F1.0 10 Tf +[<57> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702e>] TJ +ET + + +BT +306.0 265.060000000001 Td +/F1.0 10 Tf +[ 18.06640625 <66657273206f722070726f6d6f74696f6e732e>] TJ +ET + + +BT +306.0 254.190000000001 Td +/F1.0 10 Tf +[ 55.17578125 <6f7563686572206e6f742076616c696420756e74696c20746865206461792061667465722070757263686173652e>] TJ +ET + + +BT +306.0 243.320000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 232.450000000001 Td +/F1.0 10 Tf +<6d757374206265206163746976617465642062792030332f32322f323031322e> Tj +ET + + +BT +306.0 221.580000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 210.710000000001 Td +/F1.0 10 Tf +<30332f32322f323031322e> Tj +ET + + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +endstream +endobj +5 0 obj +<>stream +JFIFHHExifMM*JR(iZHH@C   +  + + +   C + +@" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?((((((((((((((((((((((((((((((((((r V I׷]s7_Oiΐ8$9k[d0+@Aʵ}b%RY܌q֖eX3ꮝa+$3HR3ġT~?h((((((((((((((((((((((((((((((((L/3'ߍ:jQA{-Bh5~y +v?ʟ_?eO>KNW|3g|`־7zWW' hFEX_n<(P*TpvVI%RUmǓ*~rM~hT? +o?kG T-O,[V~}T_#eOۃI'2G$W_fZ_8Xh|pſ}O?So+3G2G$XkfZ_8Xi~8bjo_| p7Wo+3_X-/O,[R-O,[QT?n9&¿[4*rM~ke?ᖇW,[QT?n9&¿[4*~rM~keG TkG T{5?Wo+3I p7'-ƨ[_8X>Gʟ_?n9&¿Z52pſ'2=2G$Wfe_ۃIֿ}a ?8bje=2G$W_feOۃI}a q??o5G2pſ}O?So+3I p7ᖇW,[Q q??o5G_p}S>OT? +o p7?ᖇW,[R-O,[QT_#e_ۃIֿʟ_h|pſC+-ƨkuGʿ_?n9&¿Z52pſKG T{5?#eOۃIʟai~8bjeG T{5?#eOۃIʟ_i~8bje=_| p7So+3_X-O,[R-8X>Gʟ_?n9&¿Z52pſC+-ƨkuGʟ_??n9&¿[52pſ'2pſ}E>OT? +o p7?O,Oh|pſ}O?So+3G2G$WfZ_8Xi~8bjoW'*~rM~hT? +oC'GS'-ƨk/w2G$W_fe_ۃI}a ?8bjm~8bjo_| p7So+3_W-8Y5K ?8bjoW'*~rM~hT? +oC+-Ʃ'-ƨk/w2G$WfeOۃI}c q??o5G2pſ}E>NT? o p7?ᖗ'-ƨZ_8X>Gʟ_??n9&¿[52pſKG T{5?So+3G2G$W_fZ_8X[_8X>Gʟ_?n9&¿Z52pſKG T{5?So,53G2G$WfZ_8X[_8X>Gʟa?n9&¿Z52?ᖇW,[QT?n9&¿[4*~rM~keG T-/O,[QT?n9&¿[4*~rM~kHD|opn/zm~8bjoW'*~rM~hT? +o?kG TC+-ƨk/2G$WfeOۃI}a q??o5G2=_| p7So+3_X-'G,Q q??o5G_p}Q+?n9&¿[4*~rM~ke?'-ƨk/w2G$WfeOۃI}a q??o5K q??o5G_p}Q+n9&¿Z4*~rM~ke?ᖗ'-ƨk/2G$XkfeOۃI}a ?8bjeG T{5?#eOۃIʟ_h|pſ/2pſ}E>NU? +k p7'-Ʃ?ᖇW,[QT?n9&¿[4*~rM~keG TkG T{5?Wo+3I p7'-Ʃ?ᖗ'-ƨkuGʿ_?n9&¿Z52?ᖗ'-ƨkuGʟ_??n9&¿[52?ᖇW,[QTn9&¿Z4*~rM~keG T-8X>'*~rM~hT? +oC+-ƨZ_8X>Gʟ_??n9&¿[52pſ7hp=_| p7Wo+3_X-O,[Q q??o5G_p}S>NT? +o p7ᖇO,Zq_?o5G_p}Q+?n9&¿[4*~rM~keOeG T{5?So+3G2G$W_fZq_?o5K q??o5G_p}S>NT? +o p7?ᖇW,[R-O,[QT?n9&¿[4*~rM~keG Ʃ'-ƨk/w2G$WfeOۃI}a ?8bjm~8bjoW'*rm~ƨT? +oC'GR-8X>'*rM~hT? +oKG TkG T{5;Wo+3G2G$Wf[_8Xm~8bjoW'*~rM~hT? +o?kG TC+-ƨk/2G$Wfe_ۃI}c q??o5G2pſ}E>NT? +o p7'-ƨ[_8X>Gɿʿa_e_ۃI}a q??o5K q??o5G_p}S>NU? +oWKo/Ɲ{Xƭ]K iZ9m8*TR:g=kC\>7TNG亶YZ5Tݟ( *T 漟?=iNnn4U馺sj(<(_SO{_Ӽh*!p߲%?}a-'ʧ?(k_DyO#(W^)ӼOVy4[P4qf + "m{#Qy(?i?P?qj+=& 84Ibw R+q+]Y >梱MoG&oh,ז #6 8 r۞jJ(Q^gcn:tTkCSqvW+m fZj:R׶$%H$m=_ +_ +kh^|W6J-bY$e,N +qvȚUI巟}E~'|7><_<7j:7θ4Gg-fB吢P񁸳@}o־^ kenRhT :5,fmgO=N&⓶/1_M7 l};wyw3q_ 5kz|:&RiLy2^O Ve(#[C8S+9o?I袼?_ 1mq{ŦIJHd;Nck(FU$vΚ#JsvKvzo/_ZBu[xa *mFQM4DET:R娬ȣ$t^ +!JwaX4K(@m7 +XYU}VJbfndQEdtQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ٔ\"geoV*eO*z(((((((ᧂ>/]'Ѿ<'akjOv -\*_PK6sϝ +K7F_xOo:=6&)2ic7b0"F=2okn?fE+,eoEx|mI ⮑kZ/mY4sfHBo$S<U|=M#F'>k:WmOç$s vf?LW`_}??@~ Gr' S~:<_{ѕdHwnLU<_c[^៳ [[X)붞"innVF: r05~_ +>'x#?vGt5 [TI\.V̬Jh_'MoMqWhO_7(ay{e.ughq{TwB 88R>j^ Dl,) +orx=~~q>+O%} _ڿ)h$|BU5h12nR3W|)|o<3Oh mZJ'ݫlv-샧>1| ٧̓R֕ ɴEG%w>8,[2KtPv{`W?0 +qص}_'⿇r|_+O >8Eup$(u6Yw۷OO!I5D2Mېv>ş,tۻ u K5dgr$&B!N2|ȹc2^7?O=~~|~ߌ[G{$z՝V-X`W(2:z?g u_Fk>2Y쥒x8 JW+qS|V_tUg#dNҽN?Co¾Fu{ Zo +٬፮\I*˟xϛE>|2}%/Jmil-}Q.> ߼;n>SKɀ\ÿUe_~׿?ZK| վxW:,s$hȂcÀI6sz߆?/>>(Ӿ*Ǩ[Z٥An"k&҅%rN?,~3P +;-?KPݿ ?ࡿ*|bw/kƖ+zWzZH,I.ؒnPg+_O¿|/ +}^q0i/=P# kUbd: 2J+Mk b_ ே iľ u;Hak͋X0dy{WĿ(?t^4դӢO{%kݞV{q0nvy_E(?Dvhs?c_pPX|9ҭguq8on RHǝ!fml!.jχ_x'񏄿Q/-x_>7{ Q;$[kSt土(=ȯf/ĿLz4h?G+/7*𯎮ׇE ufvd +X{_/xo᷄5nSuWJ-7g."gPYXh^&O" qmlM8®q^?g +M3ׄ~ x֙+Ebd#;nCݠY1*^}~M=!ͷVxx7_ր?t~4C> ~x6|QaeͣYq9Hč%0`GX( ^nGEg?[˹,a0(7_?1V=۾|Zī֚  F[Qۣ*(/ +Ϭ?h zxvk g<7~ W5'j;ҒK?xkß~1- &;Ib@r9Q^[8c&υZiqiv.Ν.7Nr1gqk⟍|=sAkR<^4m6NMM380s|9~:_ا7/N< {T .Qb(pJ:]GbȮ:*UjN+=*Joo|; +>n@&{UF`q]'/o7O +rNI^q!3@0;N0 =/)%W;>;gaIaZD䫍ƨU~$/Mߊ<'2xeMBMv[\65򗅿hЬecNycqkoZpxG/2|/!ç&6MYI1&c6pJɵqDNć1,b|`w0.Ny~'vg>>tZ(d.Ѐꭃ71tc0J}6Ff[߅-e3̩G3;|tܘ'+n:g/~*?u;c <:؊# + gZuq2[JOj@d &rd]NXRisRexg՟2_TS|@qȗA"ud2Pcfgm֞$/xCiܴ.dS! }Wsxo%ҡ)ȋ|Hϣ1(s܊㏋^)Iv/ rPiUbr;0M}2kzЍz_TZ?Γ_=kldHBAi +'=FMWZݢ6[Ȫ?!"]-?x\h"IWF#bҽS~+\7 +ǦhVf$B>ʒ}hbMew :u: m/ON(WiQ|-%(-͍ԑp\ ϵ~Ϳ7:i~ .>ǩO5"hextn PzWi_|*zOϳ}PԮTI]eXfM?`/aZlf⋨I[[XЬ~hس1^`s\tU(zYW,O+BڶOױLQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW8N>bϴ&mmN3V((((((((o-*ƾ>(|?+iV5oEc^ bLKq'>:wgS& +8zy^xF_\?~?Fۺ %{e }s;'hu@_A_S/?& +o{Jh_'MoMq߳ 0xI[N'o)__ 0xI+g|t?5/i_?[kOρ4ٰj_:ҿ8a6(֟y@wxG~_+\п]~~z_+o_E KP')!yE KP')!y# )^!Gۧ6z||V_tUg#dNҽN=ʾ;C~ V'3&q`/M|w^ +\h_Tt%3~OjW?K}g&ըR קdW[GPG(?DvhO?~~򋻟Om_xC@f@?WM_[ ? +p_"5g/)jHŸHhOjKK_wOfE?f/ĿLz4hȷ)?~~?B~:ZW_EطNkwǿҀ?>j!R)z?lK?j!R)z?lKRA'үNo'ҨWyx/G*?'n+ n ~f۪ +ٷNf<_EWQ@%?o_X~w |*~_kweS?5_<'UxG8IR5ΠY=+핞isaZCscp9m:Jr`;䋳Lgh?O?O<5AGtYX-o!hm1޽KQ凉ΔZT-䘼 ,vc'g?'^UZz_Z=AaUɪa;d0O Ww>xV拥|w>B\8`= NU~QKKmT #?PDq*O,qpYǿH]uxŏy$_+ +9@·F95źvUΡWVX slrFr+ S]fh<KIw)X!PRBxQޏ j4-.IC* \c#%rN3g{uW3?j9ALz65Օi3@*+sy3nOLA^;cj>д u,-CqRH_L2<]x_䴑<5k 프yIX$p 8hI5kh\khC? i5=^1$iB< +}%߅-5]oa!t! P>?ZpּCw :HcI8dBsR@87?%a[J> W|kXj>=jy&'iv-_>&kpG 93)"zxa4_nM|2xP &$9F؅}>{>x/Bg uu,'&/MҬ Ҵ>K|-Xy Q[i[PeWs_V?ƟǨ[P0Ē}c mN9Kec\('Z!Q/M~S-R\ڧ»_y;\)]đ\a3i o~IͪHOw1F'~H+Gд_Yg:Ec{j2{Vfsvf/jnZ'sx =3oswoP*(BCڌOX>j7vvrxGHpYl +8'#9⿠W^odu蚆"{f)$Z:tq`n@^6+XyT}݌gwZl~|M-+'-cK[XaZK>0 u_g/',[a xG[ٟO3?*?O|w %{?LO"?h_Wk?&^'ѰC릍tc*6d?6S}Ve$ +Ue;B:mcm?'50^/O=~~q^+WG_? 0xI+_zPj`Կu~r~_lQ_?FfJࢾn~z_+o_`5ݯ >e+44/; ,zCK3I篫4/; ,zCO3DO@LHwoE_x?E[WO;Ix_K. #im_?!+:(>U>eSj??=}aR}ʧ)گOm{@Ӽه>1{K'Q\gQE~Rʟ|௬?eDTO??i/;_X~w vW3 }d}UEWQ@Q@Q@9otPk xWx3T׬-K@H*HP{b]WÞ׿4-;Qֱ̻; 4[" 8 ܢ8}DDFuU u"ro/ |@JǓKK};$p\ 0['פQ@kσxGotO\>hvs,s8Ð O&5Ox\֮hzαdf- ͨZ)bRdb&(|+}gAo +|0 .ƄC T1cc ~ {ԡk)[5aB:8 w@QveGУpgG޷4_ ?i i~3i6Mr*[* `Y$Z2> } OެuhV]\ OD,a9'p$| '7Bѵ},5YO^8#xĬT8V9}G<6<xSGa-O!*sz!u>]Dki=F/egh˂ÃzeWIρ?,wڂjI& *RL2R$9a0}?v6gxN\Ci 6 doTX#pʊ4χчH?GGzl*0;m<`*> |ԺG*mf[-IC9HǙK+d*(uT2x5tRO]SW"Rw|Ԍ\(+<5rKt 6L{KH b`:#ЀUʐo@ᶷe-3Z}CNVVmTT*pJU@ggfᎁ:.n_iXiCeyD1nOwP~,Ǻ_IWi7*B:=< WSY+k2IucIg!Kfr,@-;Oq]E|9{c&5;o.>divl[b!>H1)ȬsoYwx'U(~˥^MۣDIS8T<_3ŚxO[O8m/u] +kX9D2Ȫy]]އN^$d!&p;$PN`kn|9MVMAӴ5;kX7/JT 6]N95j k5߄~ |=cϬ=0((((((((((((((((((((((((((((((((U3VʬfU)\}H4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEKgo*Ц?j>ٷN/xBP?f;]3b~QEzaEP/'I +[OOP%x+o?UeSW'6;Wf3XTQEqQEK*~_kweS5'I +[OOPe~0gGtQEqQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEY+̊UO#l>U((((((((((((((((((((((((((((((WO9گOm{@ӼIgo*sW_y}bOժ(L(??i/A{z[OO8kSO{w?eDT+G>>+Š(((((((((((((((((((((((((((((((( +[όӟF*Vfi9:@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@*ԟsw +j??=}aR}ʧW'6zFyj(3 +(?)eO>KNWO'ʧ'I +[DT+G>>+Š(((((((((((((((((((((((((((((((( +GN=*Wez?pgb +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(>U>eSj??=}aR}ʧW'6zFyj(3 +(?)eO>KNWxBP??i/A{z[GOPe~0gGTQEqQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEY6XcEY[όӟF*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|R}ʧW'6zOSW_y}bOժ(L(iAx+o~UU>eSj??=}aR}ʧW'6zFyj(3 +(?)e_>K;_X~w |*~_kweS5_<']Q\gQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWer?#Y6XcEY((((((((((((((((((((((((((((((?jNO9گOm{@ӼIgo*sW_y}bOժ(L(??i/A{z[OO8kSO{w?eDT+G>>+Š(((((((((((((((((((((((((((((((( +[όӟF*Vfi9:@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@*ԟsw +j??=}aR}ʧW'6zFyj(3 +(?)eO>KNWO'ʧ'I +[DT+G>>+Š(((((((((((((((((((((((((((((((( +GN=*Wez?pgb +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(>U>eSj??=}aR}ʧW'6zFyj(3 +(?)eO>KNWxBP??i/A{z[OO8k_DyO#(L((((((((((((((((((((((((((((((((~g1"f-?i#f +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(>U>eSj??=}aR}ʧ)گOm{@Ӽه>1{K'Q\gQE~Rʿ|௫eOO8+SO{weDT vW3 }d}WEWQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@c*N}9άv#ύ1Rs*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|R}ʧW'6zOSW_y}bOժ(L(??i/;_X~s +?T}_-'ʧ?(k_DyO#(L((((((((((((((((((((((((((((((((HEOU>?m9U((((((((((((((((((((((((((((((WOSW_yڗUϬ袊=0((((((((((((((((((((((((((((((((g9՚yH94QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEI?g*Цj>ٷN'x{BO?f?{g^?V=0(To~U>+Š(((((((((((((((((((((((((((((((( +#碑\Vj謻}rWb +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(>U>eSW'6zO9گOm{w?g^?V=0(T}_-'ʧ?(kWO{w~?UϬ袊=0((((((((((((((((((((((((((((((((xF=yf?(VsVh((((((((((((((((((((((((((((((_ړU<=毓j>ٷN'x{BO?f;]3b~QEzaEP/'I;x_'ʿ|௫eDT vW3 }d}WEWQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|g?.EOU7lʬPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPʿ'x{BO?f;_X~ԟsw +j??=vaϣ<^EWQ@%x+o?U>+Š(((((((((((((((((((((((((((((((( +76ml~*VИN~bEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPʿ'x{BO?f;_WԿwv +jC?=vaϣ<_EWQ@%x+o~UϬ袊=0((((((((((((((((((((((((((((((((#YSEXͻ a[?\Vh((((((((((((((((((((((((((((((_ړU<=毓j>ٷN/xBP?f߮yўf/zZ+Š(_SO{_Ӽh* С?eO>K޾eS?5_<'UQ\gQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVmh@?^1VjDkdUb +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(>U>eSU'6zOSW_yўf/zZ+Š(_SO{w?eDTN?i/Ax+o?UU=T}9U((((((((((((((((((((((((((((((WO9گOm{@ӼIgo*sW_y}bOժ(L(??i/;_Ww +CT}a-#ʧ(k_DyO#(L((((((((((((((((((((((((((((((((L?r8jVr|Tr84QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEIgo*sW_yړU<=毓j>ٷNf<_EWQ@%x+o?UKNWx]#}YUQEzaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPby*w UQv`r8jQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|R}ʧW'6zOSW_q]3b~QEzaEPߊ^Ia#d7WftͪF6Ď+:s]mx[|EgUO+w؀(pW}:x\|I|;N}ysO+FYYj])>k;t<8 ]n>Z?ٿRyLo߉ob{V?H^cO(_x[)gcvMG} kS??e&?Z"kOի%?-yLo߉<&?o71D+Z$Q A/1֧{Z/>[)kcfMI1~&?e&?Z"b{V?Hi}jOg>o{7j_3 =5}K A/1֧~LE֟KVJs>Y?ݿRLo߉_b{V?H^cO(_x}ZSϙ}7 =5}M A/1֧~LE֟KJs>Y?ٿQ1~&?e&?Z"b{V?Hi}jOg>o{7j<&7o71D+Z$Q A/1֧{Z/>[)kcfMG} kS??e&?Z"kOի%?-yLo߉?ٿWԿjG1D+Z$Qi4o33 =5g{7j^cO( kS=?V|1~&cvM_S~LEjGտϖ|&7oyL߉ob{V?H^cO(_x}ZSי}cfM_S~LEjGտϖ|&7oԾg{7j^cO( kS=?V| =7Lo߉_b{V?H^aO(_x}ZSי}3 =5}K A/1֧~LE֟KVJs>Z?ٿRyLo߉ob{V?H^cO(_x}ZSϙ}3 =5}M A/1֧~LE֟KVJs>YٿQ1~&?e&?Z"b{V?Hi}jOg>g{j_3 =5}K A/1֧~LE֟KVJs>Y?ݿQ1~&?e&?Z"b{V?Hi}jOg>o{7j<&7o70D+Z$Q A/1֧{Z/>[)gcfMK1~&?e&?Z"b{V?Hi}jOg^g{7j<&7o/1D+Z$Q A/1֧{Z/>[)gcfMG} kS??e&?Z"kOի%?,L߉?ٿWjG1D+Z$Qi4o37 =5o{7j^cO( kS=?V|1~&?ٿWԿjG1D+Z$Qi4o33 =5g{j^cO( kS=?V|1~&cfM_R~LEjGտϖ&7oԞg{j^cO( kS=?V|1~&?ٿWԿjG1D+Z$Qi4o37 =5g{j^cO( kS=?V|1~&?ٿWԿjG1D+Z$Qi4o33 =5/} kS??e&?Z"kOի%?-yLo߉<&7o71D+Z$Q A/1֧{Z/>[)kcfMG} kS??e&?Z"kOի%?,Lo߉?ݿWjG1D+Z$Qi4o33 =5g{7j^cO( kS=?W|1~&cfM_R~LEjGտϖ|&7oyL߉ob{V?H^cO(_x}ZSϙ}3 =5}M A/1֧~LE֟KVJs>Y?ݿRLo߉_a{V?H^cO(_x}^Sי}{o񯩿?e&?Z"b{V?Hi}jOg>g{j<&?o71D+Z$Q A/1֧{Z/>[)gcfMK1~&?e&?Z"b{V?Hi}jOg>o{7j_3 =5}K A/1֧~LE֟KVJs>YٿQ1~&?e&?Z"b{V?Hi}jOg^g{7j<&7o/1D+Z$Q A/1֧{Z/>[)gcvMG~&?e&?Z"b{V?Hi}jOg^g{7j<&7o/1D+Z$Q A/1֧{Z/>[)kcfMG} kS??e&?Z"kOի%?,Lo߉ٿWjG1D+Z$Qi4o33 =5g{7j^cO( kS=?V|1~&cfM_R~LEjGտϖ|&7oyL߉ob{V?H^cO(_x}ZSי}cvM_S~LEjGzOg>g{j<&?oĵ71D+Z$Q A/1֧{Z/_V|1~&cfM_S~LEjGտϖ|&?oyLo߉ob{V?H^cO(_x}^Sי}3 =5}K A/1֧~LE֟KVJs>Z?ٿQ1~&e&Z"b{V?Hi}jOgZ&淣xF6Γw #ɞ'SpEy//^a^#}khך{}.#*#,r9kb{V?H= =1h$ZҮdgZr phUE^MۨK ^IFJuQ\g +endstream +endobj +6 0 obj +</Type/Font/Subtype/TrueType/ToUnicode 8 0 R/Widths 9 0 R/FirstChar 32/FontDescriptor 10 0 R>> +endobj +8 0 obj +<>stream +x]Pˎ >(ҪЇ `R  $J{b43^Zr䃽0ud'?Fqp$'0N GLn"-Yu-3Sv_sp7)B% MI!yK@8|\FV4mGUXdҼg=3q́sCv Rk +endstream +endobj +9 0 obj +[277 750 750 750 750 750 750 750 333 333 389 750 277 333 277 750 556 556 556 556 556 750 556 556 556 556 333 750 750 750 750 750 750 722 722 722 722 750 610 750 750 277 750 750 750 833 722 750 666 750 722 666 610 722 750 943 750 750 750 750 750 750 750 750 750 556 610 556 610 556 333 610 610 277 750 556 277 889 610 610 610 610 389 556 333 610 556 777 556 556 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750] +endobj +10 0 obj +</ItalicAngle 0.0>> +endobj +11 0 obj +<>stream +x x(~''9'! !0'H" ! +82hSK&qCRGXO*G)*9無sOZ{~a #hk]z,-q[lN j3V!Tcm%Ehǒ+[;{gW[g|Flo_ٺS_Vu,im r +}#Zy :_9(x +d+־Guv#$]Z?|nWvo>WB|ѹ dG}wC̠|~{f؊d* >ͽ 1GQ&'@CWX}A +b)Ze^DVb4:f3h;|5X,Mf|:  w|/FgcgsMDLӄKu)@ܛQaF +FF^)ZG +CR0c`XQvOS0 +jM@ `x )ov#(_SS\y5_jߐ/mgW<4¼/HŒ }Y2Rv,E)HLOַKoHly8_`a̖ߥ |/XM)BP4Q40_A PBkQ;@ktXKІm]k;Vkec K6k`8Ԕ@<54vntd5SyB}BxRV@rh R:ǵqڼm̎WuiS::;ZaZUڜ+jsֶuk[ZoxSkF [MFVÙU灝iҶ[!tq, ]uh9VWx%:j ͅ\++%{^ EQCKOh)t!־VkպZ]uֱrHk_uóۻۖjs[yҢ.tiK:zVww-?,\ܱj)uY[l/`ĴZVm^^xYlg@ulB_<F&,7r5@=m-N z$~"?Ǐu"?9:& 5ul0lCBKݥ~]5 aIr+) بY]KY}'15])nf\M[YKW1kcT6V% ktIqtKR{ ?d.z/e)A1V\.AJԸ s?Rrb%gWUv&#:\YkRYf ޖZr)UaѮKqy + + 6uhW^mpYnC[ھs<uvCxiZn_t%mij;:,馊s%+KV,MNDUWiyZەW˵Pu}N5(znvuiի:Z^HAU=ݝ=Ҷu0e +mU*.3g|{ LFĄjSd3Q(otla&h3ۗtuX֝j֟zYvsZ̨=J֢1Pv&-s!ac@QYNͣQMWz#-@ӱ4,PZ׎Zlo^ +TV/t=| 󟜳aϯMڳפl.sOTo0:jA^8 m?g/4i;_s}ﳩٌ.{m1}/ka kן/?Nj:3JqD0;D鎄&?MpBH@* |($#DZySÕ'+gW*[@48h r ˆGg4n.C\lBxYQnε刬`X]~gNSW4ޯ<G=6>yzUUY+nơ0):ƕr=n⽽K8mEe_AL>ubI%c~.tY%SS|Sr7ۗa~@dME2e^nP|i$A4dn|E2F? Mmqv bKθ7JIE7X{脶rs_3K.vc!ݝX/o[ϗ/>\ k% >KpߒNp {CAGޏ}ۉULV:uc &b2a&%(r%cOx+=S8<0G6vv^ґl폺x#ȗv1L-'@[EQs!451%fE@T jv䰵Ũ'ϒq);"ʞ wW8}2Cb8ʗzlܕۯ%7Ws5O3ie7ڴnz1qSXO@\"D0j +3FsWoȻ>4tiӫ&E$Fi\s%J3^En2³8`<}< 6<U- qRE6B|CUD;/]w~ʖ7%?x\Tвh]M8O-#v}o&گ95<{/jk-6j+UwxXohɏ {{ w<`{ Kېs,048ZOX3VSO.L`aR"[,Vpr"8{!->M#vl$שy]O(S,-RIh(+-]تΛj?+g&wΙg9O-7ѓXx.}?6߀|yz7x^Uqa ɿzh<Rü+d$ɜL)@/t|;EC4l6^Ð$תUaܹ1-!zÖo (%TU [ +BI9&Q] =Pa0OaP͆261y.DJ8k@}1N|{KH4Hqzn !iň^̴$F4I2٥+ԠL$K84DTEtd8I4L$g7! iUzTOtEG?hT݃5҈p|[=w/w= ܷ4^A砗X˅bffZׂd?(@FqFKFg !j9__SRܾM3eB>f2 l|bY+x}\>W8~o@5Rpqu +}Zt|\(NWX[J+ +d7t|DQEYT=[g5Z$٪؜figgcjؠlتlݜ!*nb{74?l̚4q8NuiAJ. fjLGj"w"b\3Pq5v9ѝ9e 2pnbl +?F4SS +I(d*y^lvewͻ|Yo[YY?-τCdɦܬ3.3 +t7_ 2:&mr M| +2`{13nθ;hNTocf^z1įZ.~j);+;Ta:SM֔JS[*YL۳{Me9U1YbȧL,)+xY}ZË;{4X}FE 894Mk1zр[N܋xO/W_xGwxb:)"8XPuk[S2_7q?71[\YH]Ԁ\==L@',iSY@HF{IW,Xhu +K"zg`5A`**:N7Mɪ QR9_;{aeqJ5>iJN{<,j_tIlO-[6}8vb8o:^H|x~֫fǦ './//MkeUr]-{TƀNdp*RbAOs"X 9 tU7E*37N$]/ɒ"YA(+Ben.={ϸ?p}* &q)ǡ -jL5qiӈV֛6Ƨa 5_o"2_ޟ +=;Ad-!Znrgx " =G.%(gƿ)ߨ3 + o?BuKj|U_B>K(R5ջr@fHw$s0i(0H@n|@wQ+r)-Dag^y"SiOdm`_Cq`D0r T*ne7):FNP "$ N58uU7<Q[F3}3ao;z?6zgHSrIb䚔@5GCcl$^Wc%p`,#$B%xBB?#mZX?)|ew~L{zQM.sGKF:ƚKZ ٴ7]^kk^-}ԉ%H%6 ;e~2#ӈ}@]^mJ$MeiS:Scjڝҽj4#?0OTOPwfLk–q'糾$T<>\Y`q&Tc7Oi e22o{ v2N8rJ={{)lnM,>qg7.kz?Z߹Ƀ0*X >;}v^ai^Ev_ Ӣcck}dl7Jr/^Xo"l{̾=h?VLBb4=`w̥&Zˌba@3̀tE:ҝ @}HH *2ь)X?M +:}sd3GOïcIa Y\ųwdԷ_銆e.쌞/}J*;'ಎ{ 1^j^ڵf^`o&{RZ8(3.ffp5߽dbR B3˜Jł;e_Fc9$O bjv{RZ`,5A{i|EoM|Wg%^E5Wnq-\ք#`ONb=%}wۙȊI^>DŽʳ,;4rXg>f:(n|_=b<-dJt.lY\/rq.& U,5{ %?7[S/-K4CKf%hA2T:z_(]dyKɝmPBT(#sw&i ʺa`N@{fVk030=i7 +z21t}u+V6tjfֽY*ifڽYb% +%簵2{ "EeqAf}9|ؗڱ./p9E=xA_{4MSz Yр^2flMf /xm#NfvXJl1E1PF"YxčݴivO@Ӣé*Ur srȶFlMfG8^з*Ƙ0HړEA4ɜ|: ('9JP⑘Vp7{{6 OtQo'>{7|W>> 7lYExx}Ɩ b7:l<0x\1.%)e&gS΂+,mR1&rkƜ0{`6M|)bv{\RllcBo0&$ӱBP\il_$PDhbV ( . a*wcA **}gɔݬONtF:0eRS!-n [vg{e"rDvMSx  +_H_sC_~K:ݑв+nwecCԁa3?whemfr?yuxuMݨOZpWPFZ'ɶ`EB6i fwpN-ӥ2aYѵ>(X7Yٱ^/𒯽؋a-u.y;u-SR8+252 D9ƶ;f[o}>#6ե,tέ;o'c^ZV` }Gv H6JWY+CakE(-&*Ns쮀C`(*`d̎>95sb˓]oXSFbWt"ɋh g8 8Fh@IOG?7ٯ~ &qV!Fe>uM#U˵p$yZqN*3_Ea4K^&^? '''.F/KrTIUUAU87H_6dIT+uP$W$,!7#@MLl:o/( %kȕ率_SeM uxXm><_ ߔu7s +K\3|9x[ѷW7!=Rhf!. @^ilVF|?p-ŧˮ[V(/؅.mq19捅jTy&dԸJKy{=cЈ^ Bv$'\+$Y""Z82#?T:Q<=lI4G+-] +=>+J%u]?0/fK9rv)[1wвkAC3"'(L|Y5zEA3>|ϟ5 7E?-v BjtLIM37ڭ{%qBx ܲ5/TM2D*a7b ?Qm!l4SLƳΈO.wc%\VnƈoJ|=bzwp;HSmnKOOYq^}0Sx;i9_# | +i.p,p7yw{{]={τMֽ俉/˯yx٘q,Ww$kw^}]WMnk_9#zx1:[]0_+'*LXZ 74QMB}QM |m0X-Q֖g6[V+sT <^ Mt>y~ +}E%=6T*f} Krębm_[ KNJ|X; nɟ}*m(D/W)LAjL5rgg.OIhR8]Og=2m;Fz0sX?T6!97#îai1xLfQjoJCV%wfzKn$? ;KPn݉ԅJ85.X]5d1IԭU%O4'=U\_4U +3{/*%{p$;z -zTZK,q'lG~nnoEZzxqQt"MZpnew(ӕlFwU.v_yUͳL007Y$ y9 y'؉{9ER%ۂQs.^zb"{92D/$Ĭ'}@Z|f #.lҲzI.撫+ ua?>w}4=Sޅ:#m@ˇBz@,--p+I>#lTө*vUf7NROX.`r;9y5繛6nքۯx ̩֕}/Io{mb}1<N0ON]B(&HL#pC9?!vE ˪>+s~ůfk#;CH08ϐ 14)1-)XD*oDUntAUByܘ0toxVJ7lT*CLͺX*>+ [>*\QͰS5E+'c + C`K;JYoC/*=#Of/q0xD@YD% I&oSTmF# * +q7MGd1 jߠi P\2{Z:: ]ETrnJıgeؕx2gFh#|2#roɕ^= -~O:ad{\&| 0Jϐ1OKJ?AR_Ky|R݀UUϨ&$EuUj-x:A^PO{86Oü*񔉐H |GF+Gu;;ƅ$DD!Ӭdž0 *+ >T0޴tę8ZMJ ~"y-n9MK8}3ͧX%+DdnLS2M'<5k0J1+\5M^3mG>"!%~3Tw9U=mNkdh$HpC+:YEd0V" ƌ ;=#6B9H.ˏ)(,*[R+W?g&)٧t~T@jw-̄RV_pg'"T +OͧO5%,-LƲON>%j?A ȒCa7 0''F pcC}hQhRa(:UèWV0`Y B a FOg*S CL !~+ͽG^mB s3h#u}c UsoA 1fe\3 }a_q,zK +ڸ(FHA%f@[ + SXK6CU^Ź`Mj΍J 芣`=}d?=}%0)X8A*sR_I@{+:}VWM8m(O<gU6ydgӜd 1Py5khu4Tmr鰢\Wet/ ,pz`Y (t?))ZM撡jŹۀwǗ0!&mbBrہkہSہSaPA l"jɭG;!<+WtesK9XRb+f:2oFsIZЦu{%rl*ciyΓd )Kҁ0\f+@ +raKrCޣ&oAo_LG ohu:rn,yn|D(ȇdUAzK!C}d}&7,y/ZrROZ*cwT琗ȋH!} ,H_ 2D eh"OWsT3 gCI4'}(Yj( +=rU&T@!lu`}$WC!k \j!@7   0:0:F'hh-- 0ZF`4F`40hh`  C 0t:`耡3 0tF1`F1`3b(b(fŀQ  C 04`h1 040a +ð0aeVƟ(`qaq02q0տÀrP3ÀrParPԻ1F l@qwpw1@q8`F08ÈF0 0z0zF/`F/`2^&=(](ˬ!FZt^?D׳jT(Rh( @ *"vC˽#L-R[' nq(DN#&GA,`H -|B>ޗwj\y4TN`Q7'@(G&fW@_x\`?L(_Aa!C(P!B|j9AEv ޳}bH"<YVAV>{}}cg䩾<A)䲾ȯ&<xd:M9}6/I/Q<Í9)dODHZFx 4a@_F_aA<>xHʡ?1+)WU +Tm-s0po0p[ ?qoc]ȓ#9P.8Xh 4@}_0Qn$O 4@a9}skW@$P=G'-/xR${2>||鄴SL,MBR)eHN.[elUYEd'qu2$4Ybcܮ#us'TXKBh2PݼѺidN\e4mmjC5/㱡N 4WU6V_׶}5V~OcFWUuFڒz81v?zdj[hNDo]<8F**qy~c/^~>===ki]P]<n]|$誥 +G8W#C0 +ݴ;b:%^W"Ugt<;&Ǒ}E}&r_TL]i?XB9T@fg΂9T [i_uG׎M(1`_z븗fѦZvLgO6Z|(CkS'l$Y:@)3 D;GHR|Gȥ~n5 +nqd&IKU:p1W-6uևl#_8.rNqzs~woNK( =4)1 Mojõxz^5ʉ*ƗJKF9١HOaY-fѠ*2>˜-nдij*ZT^Zv!ROBg!UDc!-6/ :P?3Y~'˛  6ջFmjv݊S[juJhJZ0W 5@. uǞIeg&Tj\ٍ֥Sk҂1q1WZk;9Wtwᓂ1$a-t~3c4W7,e1Qh:D\[HC04j/`i€_ +i+SZ['zr!m_l šTc+Y*'gE hht'7;;.e;TmUI"v-4M +/Z@ JiЌ3OŲr /bW.n'<%}e`I3ubX/OPz/6cYjOJG<7MUtU;>E2r:SThIx$N˽xĪ1:`OY<0pDP{ָ߸Yv4 RM ^bJ95ASWڎڹn{ Ӂ~b0T+dC LRqjO TWFvcQ6C5.Hi%X)T*Tj]O1($f`=Qqї d(H+ꆉ1צ&nh0E؁r|ҸJ#&dDZzjڪ}t6f9fEMկr;]m!OF&%:Vpq.߃+GtY(e*Q"ڨ{~E9ej9{? WgKu][Fwяu:+禑I6?238joFپck^7"Ts}'> ~Aۍ%ͦ$֢v_ND? jZh)ĝ&2P-XpյJVKp *D)Vj%i:N*7I:EwYzS8zOɖ2}\*kf%*%S=`+mmo{k{?6zB^Khhеi/1PJ\ĭqܯTO[*5zD9z=,?r QBJGU;.eǼ|E5ơ +qCb8ۍGb!-1>ێq]W*B?a[70 6jm1G)ҥA|zhC$ ͞T&}c٫zDqQ2apB_(^R?cu +X>J+ƾl;Zu~X dT ڻJ<܆HmOKPo✣$\ZwUeD gy&3_f#TJDDJ&lR_{ $x2`E>.BK" ?|x]{-Bg<9/Type/Font/Subtype/TrueType/ToUnicode 8 0 R/Widths 12 0 R/FirstChar 32/FontDescriptor 13 0 R>> +endobj +12 0 obj +[277 277 354 750 750 750 750 190 333 333 389 750 277 333 277 277 556 556 556 556 556 556 556 556 556 556 277 277 750 750 750 750 1015 666 666 722 722 666 610 750 750 277 750 750 556 833 722 750 666 750 750 666 610 750 666 943 750 750 750 750 750 750 750 750 750 556 556 500 556 556 277 556 556 222 222 500 222 833 556 556 556 556 333 500 277 556 500 722 500 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 350 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 222 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750] +endobj +13 0 obj +</ItalicAngle 0.0>> +endobj +14 0 obj +<>stream +x 0Zzۜٗ30, &"@`fQ@\P5#apIB4.|$4j!#̙V9`wϽ?CwWWWo Q'Aobx:!E, }!/Мm]|x'B=P~{ / مV3Z_ e}O[CUmWuُ'n>0uҔJ˻Vfup b"PTJ?ڶ}w$hɴ< gpvȀ;aNeZ]ːO +)%;It7ˈO|ty)2 y i]LqMyIL]<{>};TvʾfG+fMFVL2ڍa;m4H|m4q[iώ(4Óae9"*B =^G翇W".L8CBz%!W qhmH!H:̇fn~ŭO|تʍ;5;K=5LtOkp31 C&;а°paIhh;Μ4O>eRRQ('=Z{+놲ǎ^8!2o;0KAʜH2h21Q +D;Ȥl2?DN59L ٿ@YTzoh/6Qjp;uv`a}d,r}cz~Y̰X]E@WPNa?BG;qnZcݺ;nXwyٗ{7Jti/e3v +,K]̃,e$ +Xc+ +BƄ0s=}'('EP,; L(|,33Qcwxp=ўyI?!2aD4dN%b Pas{S.Ke`+sE[%2'L *YM|Ȧ O y{IB#=O0QB!;۴@Xg7(9CHOh%A^AehlXL1~G URQUfZp:_gi6 H8̿ _^Fmgֻwyb s>&f,3a +?;D@~ 2@+PLI^GxTG[H8'},l~2Dtހ&3A20G70sVf=1|";I^ 9_r|Ϟ âP$|S-o\afoܴҢ |gpwICtx S ki;5 + +[G"Ҁ_a9?rM=0sI`lwqPp=x^q 'T\DR ZGܼjqCN`:1&c3,C^&@Ӄҙ3p?Ro~hR>h?[oQ-x$$cL3"dXΛZUczRE#8~Q 4NˍuP嶍`**gf|/؝s8RQۻI]90O'q^*Tq݃yA5TFxGDG2ax)5y|tMmYT{[ML}aOi+ +1){|~8>dzipS~p5&ݳ昭z.3H”H0Ix}  H5lgK%4hɂXx[I(<ur9, +E +0wnzk;wz#X{ۏ>I AϦVNKw=A ,T'^Xqѱ $0*0!BT խco3d)h䠥Y|3:{5.օn>n۔OqSl%}Kʎ|]?Soi-M6 7>tƖ%84lDP&&N1hآ cZ IS5R7D%dS%e"tbLEY%1_N +Obi¶T+Nެy eEyk'FoO<}>IC8ppg^@`d +؂]Cl4ߘn҉C 2§ qr,ږ|iIP@ٙa75%%LMupEL^̅LRyS<=y,xשge^^;](b]q_iKPRL lV`3R0#x$2#sl=81b~TJ kdG&;6;vcǠnŢu{NyzMq\*eҬWSU#6zA=}xzvc#B s0u8_5paQ#ӥ3'A$.a2]$2_&Lug:;vezqA[SWoojj.Ǵ995zgg\xTΘ1NY[ B4&:ndpc֬%5No7,,Y]ՑsxStk9[ +#sS`S +uʨb0s3Ggl v-$ 9||Cbsc/AF~!ŘCy1;]k"3vMNԙ^2nN5}jh: șl- HXujX kzk\ \_[%\^n~~JsWTi<0ܭ#?}%Y5;n~jOR׿߱3]#.X^;z*xa/2i_<>~_o|7ly[Xœ~S8I-漢w쾡ߜy~坯~WJ"[Յ7bz/:=I͜ϺӉ)e\n"G +sXkn3__MUtfXmM7iyXUyXDQ$h"Fr/e1,K$9o +0m$c s .Tǫ٨Yd̽νϱۀpz0v}ݦaܛȬ7M_;xX> +RO˧w5 azuM3=w@}wSN\9sF7gx4Y!,w7޲<\֗:T{;ɍWObf^{DƏR|0K7&oz¼Ny,DWm2p!&U( +l4̒e7QZZRY DBRPЈ&UT%x2Dj(?ajA'oh +)RZ,+aS8R&/)݄slJ^4;O$ ǗSg.ƴ/ +,-x]'7VY9&T<0+k)mB>W;.7xk.?ƇS˿Q8rnM5lƓUEoXg*+p¸֒sEm1WB| ȯ>$K3lTGm+kڔ =~_ҏY˲<,++-Ҹ=V:0=_uvC샞O'<>GQoF9l.LD%KpYh\ +ɚe4;ThKXHoHblw%L4rL &Λ_N,$BC"\R.<'ePon~?f_xڞ~c}8m0𞙀NGJ.vZf@tY'SJ9(KfgmG>Ϡ^⫨N7}rC*Fq1Q$^$NaFPEY!ƆrAA0d)6&`(&YS-0g9qnNuwo /3(u5׏>JWs??J0ǀ3|{'$IˑT䄊$`Gۊ3JTr."Pz&Pmpdt:FIqfb7vM; j cN.ac30QoGuWD+?Dwjߟf;w2 /@7BY~K,v1] Tpz$gFrOth8r<~'c&Qf94[3.3K=,vCȬ0/+Ō% ˦k6X=@98&h% s5Ӕ dV #y!׻irGCczI*msJm6`K3ŭW{R>3ώi~GyyOOz}JT , +^]e\P$IVTl$pemqMVlG[b ҃Ux(Ffs6?0O.쏐0 9Z_C= EA$ߖT~so"6Y1<#>xvy SCj<4]FQ{q04Xy8ϪFjNMߕ:ݗM6{1ܫj1~$k6=^. +RX RGNIIfۢ +Ĉ,+Ȣı6Xtw) DdnڦnPyULWuhǢ4W\4]%9.8zjŁQG)NHf,HH=4x01w^bC/O6rXmH@\/@JGa)kx,sSP +]--i>rƉivÿhX쑁,97xp*ѹBuɱ ݯ0%LXvO㧩S!R<z{eo +D#'С+S4u]WPTG+FgS4mgg]]|ϓGT2L ($2<:na7QIfEDb!b0g +y +FϴM+X.їzkԵZzse~gf6V6 +3 $Ғ`PN7V Kp!B%Q/W95AkbƘXAP(*|'t 9T#OGDWmO1gPAGWЛc(p,#\%*6(pq*Q+Rm<mxp&IBTK'a佒h% z { ީ#CJbN@>hP864P(g `9Э&_f/>7 Á}< +y-ruf@t#7#?(KR"a&ˉ^K3 ۴iyeM +x25@x6l,K} +{h(5GJd# 8+%ijԫEbȟz?t{7nf@VyA0\.AŨɺ\ѣ%zi)(ցqMzStnm撇ߎS$'#.>yQA "ҕ[CVTv(;,+qM *mOIڃ9<:T)i-aJ]G<}._HyRṔx\DPEJeUK@9dAN>;igW+'˻d8CⷄB6=,S䐸*pW!_J3P>@C"ȫ;Rlu!ҷPA8<uFP)]uJf : a&A&AMȅ34y2 ʔowLv;BA* H2BV=z~le5K]F߲~MdS>]suvŏf4moqߣG .nl>ޙwէ|0^q,P~Y.0z3`4EHۘ׌| ٻxUx[la>X"xEIjgMoDHƊwwCFП#:ݗr_71_ȿw S3N`P,D5\ŦꀽA\JqT gLVI\ gVIP q <.Z1T\W sFƧ1$b" bfE ST?U0ELsah4ä]L8[;qE~ 9L}Iu,G}2W{js4'PM[Ăj;\I}2^Vz?&Jg/{a^=1v>bOi ڳӊ|U1T HG/5q$){EW3}KO(QB㣄vL>8831q"/G5 HU$HĄQ`-lZ5`ip ʱ"j ,JuMϻ<:/ZX8˜ ( =l n2 +3c3P̻ q' u9gku"ɔꤼ\TZ[z48 +Al6V_Z=Rq +)F4@j OlJN=9,!')ݐtLGzِHЎ Y~1F1%(8TS߾=Gޛo}R6u;8SKӴwZ peٶoȰjtKƳ!qDYL-\B8,mk~vӎOءo! GLJP:N&&ŝlSMki (H!7,$Nn܈@'UV^MUMu-1恭 Q{yic*C[ۗV]ek_qiJUU_PG9+Uԡ1 ugp^Y%y]Ql{84ghICuL蒩9SKZ9Km({補ORdB]T6rd:#P[J>7ѹqM  +px!'47!ĕ3([ Qgk!HH+ek qMQ +.Q].@/}m4@Q1F. l6#,!-9q{;s +=yDBLtt2P +EL@q\υj,)pZ9[joNϯ}7ɃO^kW?=#zeA備o=6_뫿ϖ şz B,YyQ?-z]հC:G"!,?cdyѯ*ZT  :tY21,,Lu;9J7HA" Hd"`h3YgA[=zwti n /p&4h@¦_8!JZ)]_ b(Y2T;yâ~ f}T$f4N <útz#*WK VE(-7Y+NTiz;sG\7{M":|CJF0I&owGӲlM ?:!J :1,7`$bGnwp9q6w8Օi\qt8Qe8q2D髳AwH7=Ic`Vn\wIc+p:f/zE\Cϋpa]eXY*mC!J@Q8[mP',YtjɈ PPM&eAI(mg؁TMl&q &QygUUm1%7#Fd*1,{r1l| +`w?հ;8whv>/y݃qpm?:aWssb[ Ӆ2k + =:C.%3 RE$NcU`r%0yebӌlxN(A Zv.A|RyBX }5 +3Fzn KmR(^&:Y<ݱ(rYV,ee $(.oeONz_]lG$]͡l{f^F/{'Y9#MyBk~==[gK/ +4<@EPǩ%ߥ]:<ޝZջ^E&8Rz2(Q; ƨqCܫ#NIp:ͳ|9 +ú D}4G>f&3%]XV2 }}4B ya^up`zG~dfnꦡ  +Ag/33ڿv&zuw;)>Ϳşd-l^".J!b!/1x[`-ZlajQ74%jQ6fQY2 +Vnrpbhu1abTJƨ#DsL#2/|a`?_ֿ(3:OE +K=xr`?{rfnzPAP"fۺBfŰWdut!RXˠO.Yu <̾ٗ}{Ƃ+6 +8v-bl{轟ٻz}'s};>|fY['}aΖ :{4O~YasTRk[*%5 3L-j[pOƝu(;TBbN +|:̴w0T6v[V:$k.+ +5@Ҕx~U[@gvA.܏UܯKV$ETX,O 7 d2N0rڋ;[[G'Jw: +5mBGZ/WPQ/=;}d$;I*BxE%. "Ehsw|ptx6?[l6 +/ kq@x};Җ|LSBqNecQ4?I RgNqDz?I&ҁQG_~AuhȊCAW=H3#kDq +_BHS-?'DTIDK!)rf{lSXfA=DlS)552Gd)⣛ Qzk@h78xݟn?u-y^Ƈ\}?݈X~W_mIp9A/Jk;5sy96mhyYʬKm4<4> %L 158f6ia~Cȇ9G9\Ss AF# :" *H+!HRr._B>1*.+qk|g0[i?дjY~K7ޜgWw>kS-tD<S7=y#ٝǎ7~H4/T,sQ㪹n! %ɒ,YGUJHIXʵ}Z޲>wF5JDأbܟ4t(e25u -A]u=j"a7.ҋOp_6 s;z$f 9q͹;MO_VCpb|!>Lc+ly2[֗HK%uD..2HN~JpAqW^Wo*)ފ'EO+Ya8fyD~&QZ2$IgY$nϛ)h + :8+J\w2iL̉슼Hv5~ˎa"?/& N5q0#j1<;kYMf:>`.>X͎h~+_vτZ"K[j8F;+«uKq)iQSiN! \MVϭ]}2v l ;ߠ ؠ34±)ߠi7c62$"Ob.Kd}>Iw%,I>N-C]N/ JB>ru;b1ApJ6ǓCy&(1\\$+Bl3YCZn&7n܈#iCQa`zhІƭ׭[]S4bXSLkٲnI0X[^wͣ. T8qe- +_5ڼxOɯnG~i~gL) +_D +JPXM?7"aMW04ݬj( hy\M n9vqxD<. +"քWhPD:=\ՠ]~IfV)b0gWTɶz$HVUݽ +UA]:#+V- QΘ+YViӾ}ģ4?ߊe;~sBYNp(J֜rgl_|>/U`L +CĜR[%DMB}퓐?O{C D'GB841J42mѾ( +~ABf2'g/8hIT^63X%@j5ˆX (gztC'1cd{"Cd.ҍ fQ!u(AP ۰o<>TUkŕWyQw/[>wC^9[stċQ'x/!EO˓iXs5ZnZ:us c$ ?2;|D3 6ƛ^%)G=VzX,FhCX0`6tnq,p +g)i,Ac7ft%bq)W0[;-ֲI?ea;rvNrs՜>!&G<8AU5q)f[ JIO5KyFcG WGc˽K"a%ŪU"OQ-CkSiiIm?: {[xGWcM ӣ"i)"K(3#: +Z¥tRj*TT*eZgf).pj0kyLn$1 WEE1- }<2Y(٢&y\{I*^'iJA dXG') x C5uoIwS E +' eX<>uЎjGj.>+}xGɯ| +W1 Ͻˬu4ȡ1or(``UY`0ҠQHIEǞ3ȍU grnqw!D8"jȆ>9G<\݈Tr\ب܏PPczx^3eߒ@fKՐ2:(n2HQn|"(l ^((2XP :6 TA`tLE0C1_D/ʌYd?B2/X/X,,#%iHq Zb$s0Y@?0 L$qֺFRa6_te)G}Ed/FCCtd٠C*k~Qŗ48mFʒߥTx_a8l :ÖMfel T#d[9=pnc/9,R plc/qk86p< b{lsDwh!8l}4BNv:i%fI p *B3@ap7k>PTFpqu\ ݉d}ӕΩsNm[IKl` 0vRX.6c`וXiQ:cwylllȘxhU^Ri!ISJWtLRq1rƟT4tD9.񊴎|wCB!&8,ȡ,ҿ2BZɻuW#U^nu{w(3mds#8o] Nfn'3d1ܓ!]lLLe2xi)|L"vo )tXiBP d,4f,B^:F!ߧ8ƛBXqjc!7 P> # 7HG@/+ʝ1ĚpI@7J^y1\I: ++bcKUNGP.: (@GFG4 :`uSl`p09pZJ⃞,KR;96c(`ߘ&깼ӱ"R] +%T&| 5NDqD=AG Uul Yhj3ʣnt$ctTK]s~f?xK*,+$u:ްwTB;@T}cj(vF+%tnn(ZbYm(ySS3ў>mC +,(Y1 CB vԕk48,ZEFd˿YJ-Eaɱd[ZӬ6k?k5j[:PG>mOq˭񭀕c# vjϥ9TW%L*RmC6SS2OfMc;cj|]QCcR&[Kٔ-k6~sEFEZy15_ɂ29کھV*=Gb˾f+ID9?EE/v_agŃYڑj͍>%]Bv ¤]{[מo VͰi^ls6Mz6CF+7Lv Evβ 鿘M7z{&LMxOKΗs-.oچ[3Um +U6hWO~E$\vj747y[|qq~W/1ndY5W}M(uTO}ɾd_Ȉv+UpZ2hOJDtr#x93Vfwa<-URU\Y\)UĩqUq.7AUE%KۖR/ӆDKJrOίy}Z!~8[,唴=$T?p +k(e,!!n85Β!~LB˭mX +f`M3&<{%9`8Œ8D~<[coi<74GV%L6Z:DgY[>b3=֠GI{7'SVc~N6`/U ڞ^7xQۙGȾ>#G)c ܕڑkAKW 9]ek訒撵%qZʻjzc˧[=[Q~qcnj.5rDa%CW@$j)NTP>UcAկ}h=)UݒX\DI`i]rf0|l;'k +e|~-#DyVWe;"UuHޡÄn4sf1QpxcFsyyMh>{ЯO|8nkE؟@px_x)@m./ kF8V._htʙY[ + U-Uj / d5r3=,nht9lWgoH)tdz̾v[cƙ䔋^撫Y&GsUх9Yh + 3| y" `6Ve}T`so:\`$+7Ԡ5G+,!b5ˍ⢛ZlþMxCKsPBJs#-̓RsG3hԄz4Ճ.D~Yݽ?ֱK-1}U8mmCRL?WՍ"9- mle1A1YS +c6z^bEThXHt:JQe_Ň-S37%NC:υ=ջ6dk5H)8Uغv/[7uF:NƫUHWϸ%D.L{(S>|k t/98l='sj=Ih?㻏t7ől$i + YOp({jZI]2iJ<:ZDؕRjTFW F}M=Lğ)h.qKTtmKx%?MST>_#p2AItB BTtMVvI(6;MM$8huE)Jл,tA~2.| vXtT嫩@ҫŞLɦRt 4 ݃cHċJ^%{6@,V&|_wȊ#ɗ4{)l:y2?)RS~4tኸ^Qe)oٛ#^g{YqE&zcil4gal[~vU Z~^3JTMti^EifvN;H2$IeN6݆mbQβqȱfio7|?Hb(L"[vWV+CS܊GV*OOMMMכ2G_D^n[fZX:-oX#{_NLbl*-c*@+G4v-/7'9"8ߏA/aGL̢<@)l0;:D3J>zBn(GFUlօV!t*DQkt:̧4OCL7 ̓K|~ʣτi fZJ'D:%?ɟ )wtݨ[Lkl> 6 +3XVR +JִN]X*$H29W".a؉t XN~s|S?U' ڡϧmT`-Oh ekoŔ;TOjb|{ o  M)ESBߨ})V41/DqFt_=MU&D^|o>E_"Z[/zbۊjidǨ ]Ӭ&.@~B0-6 0Z7L2]FXYKLuȍy_V)@o,f}BB}+AFӀx`:M ALMi(]l=5A7 4 6 ':VkQZǎBJwRt"ƿ +GYO~y +8qu2b +endstream +endobj +3 0 obj +<> +endobj +15 0 obj +<>/Font<>>>/MediaBox[0 482 361 792]>> +endobj +16 0 obj +<>/Font<>>>/MediaBox[357 490 612 631]>> +endobj +19 0 obj +<> +endobj +17 0 obj +<>stream +0.000 0.000 0.000 rg +0.000 0.000 0.000 RG +q +1.000 0.000 0.000 RG + +q +561.600 0 0 254.991 25.200 501.009 cm +/I1 Do +Q + +BT +380.88 700.456 Td +/F2.0 13 Tf +<373238363438313231382d6d656d62> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 681.008 Td +/F2.0 14 Tf +<4d6574726f526f636b> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 661.502 Td +/F2.0 10 Tf +<5768617420796f7520676574> Tj +ET + + +BT +43.2 650.632 Td +/F1.0 10 Tf +<31206d6f6e7468206d656d626572736869702c20756e6c696d697465642072656e74616c7320616e6420626567696e6e65727320636c696d62696e6720636c617373> Tj +ET + + +BT +43.2 632.562 Td +/F2.0 10 Tf +<52656465656d61626c652061742074686520666f6c6c6f77696e67206c6f636174696f6e287329> Tj +ET + + +BT +43.2 621.692 Td +/F1.0 10 Tf +<3639204e6f726d616e2053747265657420457665726574742c204d41> Tj +ET + + +BT +43.2 596.422 Td +/F1.0 10 Tf +<20> Tj +ET + + +BT +381.6 665.92 Td +/F2.0 10 Tf +[<5075726368617365642062793a2053616d2054> 74.21875 <6f62696e2d686f63687374616474>] TJ +ET + +0.000 0.000 0.000 rg + +BT +381.6 575.92 Td +/F2.0 10 Tf +<497373756564206279204d6574726f526f636b> Tj +ET + + +BT +381.6 565.05 Td +/F2.0 10 Tf +[<497373756564206f6e20546875205365702032322031393a33303a33302055544320323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 554.18 Td +/F2.0 10 Tf +<4e6f742076616c696420756e74696c3a20467269205365702032332031393a33303a333020555443> Tj +ET + + +BT +381.6 543.31 Td +/F2.0 10 Tf +[<323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 532.44 Td +/F2.0 10 Tf +<50726f6d6f74696f6e616c2076616c756520657870697265733a20546875204d6172203232> Tj +ET + + +BT +381.6 521.57 Td +/F2.0 10 Tf +<30303a30303a3030202d3034303020323031322a> Tj +ET + +0.000 0.000 0.000 rg + +BT +36.0 488.064 Td +/F2.0 12 Tf +<4d6574726f526f636b20284d656d6229> Tj +ET + + +BT +36.0 467.92 Td +/F2.0 10 Tf +<496e737472756374696f6e73> Tj +ET + + +BT +36.0 449.85 Td +/F1.0 10 Tf +<2a20506c656173652063616c6c203631372d3338372d3736323520284576657265747429206f72203937382d3439392d37363235> Tj +ET + + +BT +36.0 438.98 Td +/F1.0 10 Tf +<284e657762757279706f72742920746f207265736572766520636c6173736573206f72206368616c6c656e676520636f757273652074696d652e> Tj +ET + + +BT +36.0 428.11 Td +/F1.0 10 Tf +[<466f722073687574746c6520736572766963652066726f6d207468652057> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702c20706c656173652063616c6c>] TJ +ET + + +BT +36.0 417.24 Td +/F1.0 10 Tf +<3631372d3338372d373632352075706f6e206172726976616c206174207468652073746174696f6e2e> Tj +ET + + +BT +36.0 406.37 Td +/F1.0 10 Tf +<2a205072696e7420766f756368657220616e64206272696e6720746f2061206d65726368616e74206c6f636174696f6e206c6973746564206f6e> Tj +ET + + +BT +36.0 395.5 Td +/F1.0 10 Tf +[<7468697320766f7563686572> 55.17578125 <2e>] TJ +ET + + +BT +36.0 384.63 Td +/F1.0 10 Tf +[<2a2057> 18.06640625 <7269746520796f7572206e616d6520616e642066756c6c206164647265737320666f756e64206f6e20796f757220494420696e20746865>] TJ +ET + + +BT +36.0 373.760000000001 Td +/F1.0 10 Tf +<73706163652070726f76696465642062656c6f77202874686520706572736f6e2072656465656d696e67207468697320766f7563686572> Tj +ET + + +BT +36.0 362.890000000001 Td +/F1.0 10 Tf +<73686f756c642070726f76696465207468697320696e666f726d6174696f6e2c206e6f74206e65636573736172696c7920746865> Tj +ET + + +BT +36.0 352.020000000001 Td +/F1.0 10 Tf +<70757263686173657220696620676976656e20617320612067696674292e20> Tj +ET + + +BT +36.0 341.150000000001 Td +/F1.0 10 Tf +<2a2050726573656e742076616c69642c20676f7665726e6d656e742d6973737565642070686f746f204944207768656e> Tj +ET + + +BT +36.0 330.280000000001 Td +/F1.0 10 Tf +<72656465656d696e6720796f757220766f75636865727320286e616d65206f6e20494420646f6573206e6f74206861766520746f> Tj +ET + + +BT +36.0 319.410000000001 Td +/F1.0 10 Tf +<6d6174636820746865207075726368617365722773206e616d6520696620676976656e20617320612067696674292e> Tj +ET + + +BT +36.0 308.540000000001 Td +ET + + +BT +36.0 297.670000000001 Td +/F1.0 10 Tf +[<496e737472756374696f6e7320666f72206d65726368616e743a2042757957> -0.0 <6974684d652068617320616c726561647920636f6c6c6563746564>] TJ +ET + + +BT +36.0 286.800000000001 Td +/F1.0 10 Tf +<7061796d656e74206f6e20796f757220626568616c662e> Tj +ET + + +BT +306.0 467.92 Td +/F2.0 10 Tf +<44657461696c73> Tj +ET + + +BT +306.0 449.85 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 438.98 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 428.11 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 417.24 Td +/F1.0 10 Tf +<6d656d6265727368697020626567696e732066726f6d20796f75722066697273742076697369743b20706c656173652070726573656e74> Tj +ET + + +BT +306.0 406.37 Td +/F1.0 10 Tf +<766f756368657220746f2061637469766174652e20> Tj +ET + + +BT +306.0 395.5 Td +/F1.0 10 Tf +[ -37.109375 18.06640625 <7320636c6173732e20> 55.17578125 <41> 55.17578125 <20636c617373206973206e6f74>] TJ +ET + + +BT +306.0 384.63 Td +/F1.0 10 Tf +[<6e656365737361727920696620796f7520616c7265616479206b6e6f7720686f7720746f2062656c6179> 74.21875 <2e>] TJ +ET + + +BT +306.0 373.760000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 362.890000000001 Td +/F1.0 10 Tf +<70756e636820636172642e> Tj +ET + + +BT +306.0 352.020000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 341.150000000001 Td +/F1.0 10 Tf +<6c6f636174696f6e206f6e6c793b206d757374206265206174206c6561737420342720362220746f20676f207468726f7567682074686520636f757273652e> Tj +ET + + +BT +306.0 330.280000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 319.410000000001 Td +/F1.0 10 Tf +[<617661696c6162696c697479> 74.21875 <2e>] TJ +ET + + +BT +306.0 308.540000000001 Td +/F1.0 10 Tf +[ 55.17578125 <4167657320313820616e6420756e646572206d7573742068617665206120776169766572207369676e6564206279206120706172656e74>] TJ +ET + + +BT +306.0 297.670000000001 Td +/F1.0 10 Tf +<6f72206c6567616c20677561726469616e2e> Tj +ET + + +BT +306.0 286.800000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 275.930000000001 Td +/F1.0 10 Tf +[<57> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702e>] TJ +ET + + +BT +306.0 265.060000000001 Td +/F1.0 10 Tf +[ 18.06640625 <66657273206f722070726f6d6f74696f6e732e>] TJ +ET + + +BT +306.0 254.190000000001 Td +/F1.0 10 Tf +[ 55.17578125 <6f7563686572206e6f742076616c696420756e74696c20746865206461792061667465722070757263686173652e>] TJ +ET + + +BT +306.0 243.320000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 232.450000000001 Td +/F1.0 10 Tf +<6d757374206265206163746976617465642062792030332f32322f323031322e> Tj +ET + + +BT +306.0 221.580000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 210.710000000001 Td +/F1.0 10 Tf +<30332f32322f323031322e> Tj +ET + + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +endstream +endobj +18 0 obj +<>stream +0.000 0.000 0.000 rg +0.000 0.000 0.000 RG +q +1.000 0.000 0.000 RG + +q +561.600 0 0 254.991 25.200 501.009 cm +/I1 Do +Q + +BT +380.88 700.456 Td +/F2.0 13 Tf +<373238363438313231382d6d656d62> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 681.008 Td +/F2.0 14 Tf +<4d6574726f526f636b> Tj +ET + +0.000 0.000 0.000 rg + +BT +43.2 661.502 Td +/F2.0 10 Tf +<5768617420796f7520676574> Tj +ET + + +BT +43.2 650.632 Td +/F1.0 10 Tf +<31206d6f6e7468206d656d626572736869702c20756e6c696d697465642072656e74616c7320616e6420626567696e6e65727320636c696d62696e6720636c617373> Tj +ET + + +BT +43.2 632.562 Td +/F2.0 10 Tf +<52656465656d61626c652061742074686520666f6c6c6f77696e67206c6f636174696f6e287329> Tj +ET + + +BT +43.2 621.692 Td +/F1.0 10 Tf +<3639204e6f726d616e2053747265657420457665726574742c204d41> Tj +ET + + +BT +43.2 596.422 Td +/F1.0 10 Tf +<20> Tj +ET + + +BT +381.6 665.92 Td +/F2.0 10 Tf +[<5075726368617365642062793a2053616d2054> 74.21875 <6f62696e2d686f63687374616474>] TJ +ET + +0.000 0.000 0.000 rg + +BT +381.6 575.92 Td +/F2.0 10 Tf +<497373756564206279204d6574726f526f636b> Tj +ET + + +BT +381.6 565.05 Td +/F2.0 10 Tf +[<497373756564206f6e20546875205365702032322031393a33303a33302055544320323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 554.18 Td +/F2.0 10 Tf +<4e6f742076616c696420756e74696c3a20467269205365702032332031393a33303a333020555443> Tj +ET + + +BT +381.6 543.31 Td +/F2.0 10 Tf +[<323031> 55.17578125 <31>] TJ +ET + + +BT +381.6 532.44 Td +/F2.0 10 Tf +<50726f6d6f74696f6e616c2076616c756520657870697265733a20546875204d6172203232> Tj +ET + + +BT +381.6 521.57 Td +/F2.0 10 Tf +<30303a30303a3030202d3034303020323031322a> Tj +ET + +0.000 0.000 0.000 rg + +BT +36.0 488.064 Td +/F2.0 12 Tf +<4d6574726f526f636b20284d656d6229> Tj +ET + + +BT +36.0 467.92 Td +/F2.0 10 Tf +<496e737472756374696f6e73> Tj +ET + + +BT +36.0 449.85 Td +/F1.0 10 Tf +<2a20506c656173652063616c6c203631372d3338372d3736323520284576657265747429206f72203937382d3439392d37363235> Tj +ET + + +BT +36.0 438.98 Td +/F1.0 10 Tf +<284e657762757279706f72742920746f207265736572766520636c6173736573206f72206368616c6c656e676520636f757273652074696d652e> Tj +ET + + +BT +36.0 428.11 Td +/F1.0 10 Tf +[<466f722073687574746c6520736572766963652066726f6d207468652057> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702c20706c656173652063616c6c>] TJ +ET + + +BT +36.0 417.24 Td +/F1.0 10 Tf +<3631372d3338372d373632352075706f6e206172726976616c206174207468652073746174696f6e2e> Tj +ET + + +BT +36.0 406.37 Td +/F1.0 10 Tf +<2a205072696e7420766f756368657220616e64206272696e6720746f2061206d65726368616e74206c6f636174696f6e206c6973746564206f6e> Tj +ET + + +BT +36.0 395.5 Td +/F1.0 10 Tf +[<7468697320766f7563686572> 55.17578125 <2e>] TJ +ET + + +BT +36.0 384.63 Td +/F1.0 10 Tf +[<2a2057> 18.06640625 <7269746520796f7572206e616d6520616e642066756c6c206164647265737320666f756e64206f6e20796f757220494420696e20746865>] TJ +ET + + +BT +36.0 373.760000000001 Td +/F1.0 10 Tf +<73706163652070726f76696465642062656c6f77202874686520706572736f6e2072656465656d696e67207468697320766f7563686572> Tj +ET + + +BT +36.0 362.890000000001 Td +/F1.0 10 Tf +<73686f756c642070726f76696465207468697320696e666f726d6174696f6e2c206e6f74206e65636573736172696c7920746865> Tj +ET + + +BT +36.0 352.020000000001 Td +/F1.0 10 Tf +<70757263686173657220696620676976656e20617320612067696674292e20> Tj +ET + + +BT +36.0 341.150000000001 Td +/F1.0 10 Tf +<2a2050726573656e742076616c69642c20676f7665726e6d656e742d6973737565642070686f746f204944207768656e> Tj +ET + + +BT +36.0 330.280000000001 Td +/F1.0 10 Tf +<72656465656d696e6720796f757220766f75636865727320286e616d65206f6e20494420646f6573206e6f74206861766520746f> Tj +ET + + +BT +36.0 319.410000000001 Td +/F1.0 10 Tf +<6d6174636820746865207075726368617365722773206e616d6520696620676976656e20617320612067696674292e> Tj +ET + + +BT +36.0 308.540000000001 Td +ET + + +BT +36.0 297.670000000001 Td +/F1.0 10 Tf +[<496e737472756374696f6e7320666f72206d65726368616e743a2042757957> -0.0 <6974684d652068617320616c726561647920636f6c6c6563746564>] TJ +ET + + +BT +36.0 286.800000000001 Td +/F1.0 10 Tf +<7061796d656e74206f6e20796f757220626568616c662e> Tj +ET + + +BT +306.0 467.92 Td +/F2.0 10 Tf +<44657461696c73> Tj +ET + + +BT +306.0 449.85 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 438.98 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 428.11 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 417.24 Td +/F1.0 10 Tf +<6d656d6265727368697020626567696e732066726f6d20796f75722066697273742076697369743b20706c656173652070726573656e74> Tj +ET + + +BT +306.0 406.37 Td +/F1.0 10 Tf +<766f756368657220746f2061637469766174652e20> Tj +ET + + +BT +306.0 395.5 Td +/F1.0 10 Tf +[ -37.109375 18.06640625 <7320636c6173732e20> 55.17578125 <41> 55.17578125 <20636c617373206973206e6f74>] TJ +ET + + +BT +306.0 384.63 Td +/F1.0 10 Tf +[<6e656365737361727920696620796f7520616c7265616479206b6e6f7720686f7720746f2062656c6179> 74.21875 <2e>] TJ +ET + + +BT +306.0 373.760000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 362.890000000001 Td +/F1.0 10 Tf +<70756e636820636172642e> Tj +ET + + +BT +306.0 352.020000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 341.150000000001 Td +/F1.0 10 Tf +<6c6f636174696f6e206f6e6c793b206d757374206265206174206c6561737420342720362220746f20676f207468726f7567682074686520636f757273652e> Tj +ET + + +BT +306.0 330.280000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 319.410000000001 Td +/F1.0 10 Tf +[<617661696c6162696c697479> 74.21875 <2e>] TJ +ET + + +BT +306.0 308.540000000001 Td +/F1.0 10 Tf +[ 55.17578125 <4167657320313820616e6420756e646572206d7573742068617665206120776169766572207369676e6564206279206120706172656e74>] TJ +ET + + +BT +306.0 297.670000000001 Td +/F1.0 10 Tf +<6f72206c6567616c20677561726469616e2e> Tj +ET + + +BT +306.0 286.800000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 275.930000000001 Td +/F1.0 10 Tf +[<57> 18.06640625 <656c6c696e67746f6e20> 18.06640625 <54> 18.06640625 <2073746f702e>] TJ +ET + + +BT +306.0 265.060000000001 Td +/F1.0 10 Tf +[ 18.06640625 <66657273206f722070726f6d6f74696f6e732e>] TJ +ET + + +BT +306.0 254.190000000001 Td +/F1.0 10 Tf +[ 55.17578125 <6f7563686572206e6f742076616c696420756e74696c20746865206461792061667465722070757263686173652e>] TJ +ET + + +BT +306.0 243.320000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 232.450000000001 Td +/F1.0 10 Tf +<6d757374206265206163746976617465642062792030332f32322f323031322e> Tj +ET + + +BT +306.0 221.580000000001 Td +/F1.0 10 Tf + Tj +ET + + +BT +306.0 210.710000000001 Td +/F1.0 10 Tf +<30332f32322f323031322e> Tj +ET + + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +BT +36.0 138.176 Td +/F2.0 8 Tf +[<4265666f72652072656465656d696e67207468697320766f7563686572> 55.17578125 <2c20706c6561736520777269746520696e207468652072657175657374656420696e666f726d6174696f6e2062656c6f77> 37.109375 <2e2020> 37.109375 <4174207468652074696d65206f6620726564656d7074696f6e2c20796f75206d7573742070726573656e7420612076616c69642c>] TJ +ET + + +BT +36.0 129.48 Td +/F2.0 8 Tf +<676f7665726e6d656e742d6973737565642070686f746f2049442074686174206d617463686573207468697320696e666f726d6174696f6e2e> Tj +ET + +0.749 0.749 0.749 RG +36.000 86.400 216.000 36.000 re +S + +BT +36.0 76.976 Td +/F1.0 8 Tf +<506c6561736520777269746520796f7572206e616d652068657265> Tj +ET + +252.000 86.400 216.000 36.000 re +S + +BT +252.0 76.976 Td +/F1.0 8 Tf +[<46756c6c20> 55.17578125 <41646472657373206173206c6973746564206f6e20796f7572204944>] TJ +ET + +468.000 86.400 108.000 36.000 re +S + +BT +468.0 76.976 Td +/F1.0 8 Tf +<5374617465206c6973746564206f6e20796f7572204944> Tj +ET + + +BT +239.576 66.176 Td +/F1.0 8 Tf +[<5468616e6b20796f7520666f72207573696e672042757957> -0.0 <6974684d652e636f6d21>] TJ +ET + + +BT +151.5 57.48 Td +/F1.0 8 Tf +<496620796f75206e65656420616e7920617373697374616e63652c20796f752063616e20726561636820757320617420696e666f40627579776974686d652e636f6d206f72203836362d3638302d37303038> Tj +ET + + +BT +67.292703125 48.784 Td +/F1.0 8 Tf +[<2a416674657220746869732065787069726174696f6e20646174652c20796f75206d6179207374696c6c2062652061626c6520746f2072656465656d207468697320766f756368657220666f7220746865207072696365206f726967696e616c6c79207061696420666f722069742c206966207265717569726564206279206c6177> 55.17578125 <2e20506c6561736520736565>] TJ +ET + + +BT +220.912 40.088 Td +/F1.0 8 Tf +<687474703a2f2f627579776974686d652e636f6d2f70616765732f66617120666f72206d6f72652064657461696c73> Tj +ET + +Q + +endstream +endobj +20 0 obj +<> +endobj +xref +0 21 +0000000000 65535 f +0000000015 00000 n +0000000196 00000 n +0000095543 00000 n +0000000230 00000 n +0000011046 00000 n +0000048495 00000 n +0000070626 00000 n +0000048758 00000 n +0000049039 00000 n +0000049952 00000 n +0000050266 00000 n +0000070850 00000 n +0000071765 00000 n +0000072039 00000 n +0000095620 00000 n +0000095807 00000 n +0000096044 00000 n +0000106861 00000 n +0000095998 00000 n +0000117678 00000 n +trailer +<<65e6ebcb3171fabdc484bf86c3dc1c63>]/Info 20 0 R/Size 21>> +startxref +117824 +%%EOF diff --git a/test/pdfs/preistabelle.pdf.link b/test/pdfs/preistabelle.pdf.link new file mode 100644 index 000000000..f48f8565c --- /dev/null +++ b/test/pdfs/preistabelle.pdf.link @@ -0,0 +1 @@ +http://www.fyve.de/downloads/Preistabelle_FYVE_Oktober_2010.pdf diff --git a/test/pdfs/usmanm-bad.pdf.link b/test/pdfs/usmanm-bad.pdf.link index f7ebc3610..e37e5aa2b 100644 --- a/test/pdfs/usmanm-bad.pdf.link +++ b/test/pdfs/usmanm-bad.pdf.link @@ -1 +1 @@ -http://www.mit.edu/~6.033/writing-samples/usmanm_dp1.pdf +http://web.mit.edu/6.033/2011/wwwdocs/writing-samples/usmanm_dp1.pdf diff --git a/test/pdfs/wnv_chinese.pdf.link b/test/pdfs/wnv_chinese.pdf.link index fbbc81760..0bd2af8f6 100644 --- a/test/pdfs/wnv_chinese.pdf.link +++ b/test/pdfs/wnv_chinese.pdf.link @@ -1 +1 @@ -http://www.cdc.gov/ncidod/dvbid/westnile/languages/chinese.pdf +http://web.archive.org/web/20110623114753/http://www.cdc.gov/ncidod/dvbid/westnile/languages/chinese.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index f3c045ccf..a4c6defcc 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -480,6 +480,20 @@ "link": true, "type": "eq" }, + { "id": "preistabelle", + "file": "pdfs/preistabelle.pdf", + "md5": "d2f0b2086160d4f3d325c79a5dc1fb4d", + "rounds": 1, + "pageLimit": 2, + "link": true, + "type": "eq" + }, + { "id": "issue1350", + "file": "pdfs/issue1350.pdf", + "md5": "92f72a04a4d9d05b2dd433b51f32ab1f", + "rounds": 1, + "type": "eq" + }, { "id": "issue925", "file": "pdfs/issue925.pdf", "md5": "f58fe943090aff89dcc8e771bc0db4c2", @@ -508,6 +522,13 @@ "link": true, "type": "eq" }, + { "id": "issue1002", + "file": "pdfs/issue1002.pdf", + "md5": "af62d6cd95079322d4af18edd960d15c", + "rounds": 1, + "link": false, + "type": "eq" + }, { "id": "issue1243", "file": "pdfs/issue1243.pdf", "md5": "130c849b83513d5ac5e03c6421fc7489", @@ -523,5 +544,19 @@ "pageLimit": 2, "link": true, "type": "eq" + }, + { "id": "issue1309", + "file": "pdfs/issue1309.pdf", + "md5": "e835fb7f3dab3073ad37d0bd3c6399fa", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1317", + "file": "pdfs/issue1317.pdf", + "md5": "6fb46275b30c48c8985617d4f86199e3", + "rounds": 1, + "link": true, + "type": "eq" } ] diff --git a/test/unit/font_spec.js b/test/unit/font_spec.js new file mode 100644 index 000000000..9f0969324 --- /dev/null +++ b/test/unit/font_spec.js @@ -0,0 +1,223 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +describe('font', function() { + function hexDump(bytes) { + var line = ''; + for (var i = 0, ii = bytes.length; i < ii; ++i) { + var b = bytes[i].toString(16); + if (b.length < 2) + b = '0' + b; + line += b.toString(16); + } + return line; + } + // This example font comes from the CFF spec: + // http://www.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf + var exampleFont = '0100040100010101134142434445462b' + + '54696d65732d526f6d616e000101011f' + + 'f81b00f81c02f81d03f819041c6f000d' + + 'fb3cfb6efa7cfa1605e911b8f1120003' + + '01010813183030312e30303754696d65' + + '7320526f6d616e54696d657300000002' + + '010102030e0e7d99f92a99fb7695f773' + + '8b06f79a93fc7c8c077d99f85695f75e' + + '9908fb6e8cf87393f7108b09a70adf0b' + + 'f78e14'; + var fontData = []; + for (var i = 0; i < exampleFont.length; i += 2) { + var hex = exampleFont.substr(i, 2); + fontData.push(parseInt(hex, 16)); + } + var bytes = new Uint8Array(fontData); + fontData = {getBytes: function() { return bytes}}; + + function bytesToString(bytesArray) { + var str = ''; + for (var i = 0, ii = bytesArray.length; i < ii; i++) + str += String.fromCharCode(bytesArray[i]); + return str; + } + + describe('CFFParser', function() { + var parser = new CFFParser(fontData); + var cff = parser.parse(); + + it('parses header', function() { + var header = cff.header; + expect(header.major).toEqual(1); + expect(header.minor).toEqual(0); + expect(header.hdrSize).toEqual(4); + expect(header.offSize).toEqual(1); + }); + + it('parses name index', function() { + var names = cff.names; + expect(names.length).toEqual(1); + expect(names[0]).toEqual('ABCDEF+Times-Roman'); + }); + + it('sanitizes name index', function() { + var index = new CFFIndex(); + index.add(['['.charCodeAt(0), 'a'.charCodeAt(0)]); + + var names = parser.parseNameIndex(index); + expect(names).toEqual(['_a']); + + index = new CFFIndex(); + var longName = []; + for (var i = 0; i < 129; i++) + longName.push(0); + index.add(longName); + names = parser.parseNameIndex(index); + expect(names[0].length).toEqual(127); + }); + + it('parses string index', function() { + var strings = cff.strings; + expect(strings.count).toEqual(3); + expect(strings.get(0)).toEqual('.notdef'); + expect(strings.get(391)).toEqual('001.007'); + }); + + it('parses top dict', function() { + var topDict = cff.topDict; + // 391 version 392 FullName 393 FamilyName 389 Weight 28416 UniqueID + // -168 -218 1000 898 FontBBox 94 CharStrings 45 102 Private + expect(topDict.getByName('version')).toEqual(391); + expect(topDict.getByName('FullName')).toEqual(392); + expect(topDict.getByName('FamilyName')).toEqual(393); + expect(topDict.getByName('Weight')).toEqual(389); + expect(topDict.getByName('UniqueID')).toEqual(28416); + expect(topDict.getByName('FontBBox')).toEqual([-168, -218, 1000, 898]); + expect(topDict.getByName('CharStrings')).toEqual(94); + expect(topDict.getByName('Private')).toEqual([45, 102]); + }); + + it('parses predefined charsets', function() { + var charset = parser.parseCharsets(0, 0, null, true); + expect(charset.predefined).toEqual(true); + }); + + it('parses charset format 0', function() { + // The first three bytes make the offset large enough to skip predefined. + var bytes = new Uint8Array([0x00, 0x00, 0x00, + 0x00, // format + 0x00, 0x02 // sid/cid + ]); + parser.bytes = bytes; + var charset = parser.parseCharsets(3, 2, new CFFStrings(), false); + expect(charset.charset[1]).toEqual('exclam'); + + // CID font + var charset = parser.parseCharsets(3, 2, new CFFStrings(), true); + expect(charset.charset[1]).toEqual(2); + }); + + it('parses charset format 1', function() { + // The first three bytes make the offset large enough to skip predefined. + var bytes = new Uint8Array([0x00, 0x00, 0x00, + 0x01, // format + 0x00, 0x08, // sid/cid start + 0x01 // sid/cid left + ]); + parser.bytes = bytes; + var charset = parser.parseCharsets(3, 2, new CFFStrings(), false); + expect(charset.charset).toEqual(['.notdef', 'quoteright', 'parenleft']); + + // CID font + var charset = parser.parseCharsets(3, 2, new CFFStrings(), true); + expect(charset.charset).toEqual(['.notdef', 8, 9]); + }); + + it('parses charset format 2', function() { + // format 2 is the same as format 1 but the left is card16 + // The first three bytes make the offset large enough to skip predefined. + var bytes = new Uint8Array([0x00, 0x00, 0x00, + 0x02, // format + 0x00, 0x08, // sid/cid start + 0x00, 0x01 // sid/cid left + ]); + parser.bytes = bytes; + var charset = parser.parseCharsets(3, 2, new CFFStrings(), false); + expect(charset.charset).toEqual(['.notdef', 'quoteright', 'parenleft']); + + // CID font + var charset = parser.parseCharsets(3, 2, new CFFStrings(), true); + expect(charset.charset).toEqual(['.notdef', 8, 9]); + }); + + it('parses encoding format 0', function() { + // The first two bytes make the offset large enough to skip predefined. + var bytes = new Uint8Array([0x00, 0x00, + 0x00, // format + 0x01, // count + 0x08 // start + ]); + parser.bytes = bytes; + var encoding = parser.parseEncoding(2, {}, new CFFStrings(), null); + expect(encoding.encoding).toEqual({0x8: 1}); + }); + + it('parses encoding format 1', function() { + // The first two bytes make the offset large enough to skip predefined. + var bytes = new Uint8Array([0x00, 0x00, + 0x01, // format + 0x01, // num ranges + 0x07, // range1 start + 0x01 // range2 left + ]); + parser.bytes = bytes; + var encoding = parser.parseEncoding(2, {}, new CFFStrings(), null); + expect(encoding.encoding).toEqual({0x7: 0x01, 0x08: 0x02}); + }); + + it('parses fdselect format 0', function() { + var bytes = new Uint8Array([0x00, // format + 0x00, // gid: 0 fd: 0 + 0x01 // gid: 1 fd: 1 + ]); + parser.bytes = bytes; + var fdSelect = parser.parseFDSelect(0, 2); + expect(fdSelect.fdSelect).toEqual([0, 1]); + }); + + it('parses fdselect format 3', function() { + var bytes = new Uint8Array([0x03, // format + 0x00, 0x02, // range count + 0x00, 0x00, // first gid + 0x09, // font dict 1 id + 0x00, 0x02, // nex gid + 0x0a, // font dict 2 gid + 0x00, 0x04 // sentinel (last gid) + ]); + parser.bytes = bytes; + var fdSelect = parser.parseFDSelect(0, 2); + expect(fdSelect.fdSelect).toEqual([9, 9, 0xa, 0xa]); + }); + // TODO fdArray + }); + describe('CFFCompiler', function() { + it('encodes integers', function() { + var c = new CFFCompiler(); + // all the examples from the spec + expect(c.encodeInteger(0)).toEqual([0x8b]); + expect(c.encodeInteger(100)).toEqual([0xef]); + expect(c.encodeInteger(-100)).toEqual([0x27]); + expect(c.encodeInteger(1000)).toEqual([0xfa, 0x7c]); + expect(c.encodeInteger(-1000)).toEqual([0xfe, 0x7c]); + expect(c.encodeInteger(10000)).toEqual([0x1c, 0x27, 0x10]); + expect(c.encodeInteger(-10000)).toEqual([0x1c, 0xd8, 0xf0]); + expect(c.encodeInteger(100000)).toEqual([0x1d, 0x00, 0x01, 0x86, 0xa0]); + expect(c.encodeInteger(-100000)).toEqual([0x1d, 0xff, 0xfe, 0x79, 0x60]); + }); + it('encodes floats', function() { + var c = new CFFCompiler(); + expect(c.encodeFloat(-2.25)).toEqual([0x1e, 0xe2, 0xa2, 0x5f]); + expect(c.encodeFloat(5e-11)).toEqual([0x1e, 0x5c, 0x11, 0xff]); + }); + // TODO a lot more compiler tests + }); +}); diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf index b2d3e86f1..9a26df6a4 100644 --- a/test/unit/jsTestDriver.conf +++ b/test/unit/jsTestDriver.conf @@ -25,6 +25,7 @@ load: - ../../src/bidi.js - ../../external/jpgjs/jpg.js - ../unit/obj_spec.js + - ../unit/font_spec.js - ../unit/function_spec.js - ../unit/crypto_spec.js - ../unit/stream_spec.js diff --git a/web/viewer.css b/web/viewer.css index fdce0288a..9a0cf388c 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -391,11 +391,43 @@ canvas { } } -#loading { +#loadingBox { margin: 100px 0; text-align: center; } +#loadingBar { + background-color: #333; + display: inline-block; + border: 1px solid black; + clear: both; + margin:0px; + line-height: 0; + border-radius: 4px; + width: 15em; + height: 1.5em; +} + +#loadingBar .progress { + background-color: green; + display: inline-block; + float: left; + + background: #b4e391; + background: -moz-linear-gradient(top, #b4e391 0%, #61c419 50%, #b4e391 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b4e391), color-stop(50%,#61c419), color-stop(100%,#b4e391)); + background: -webkit-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%); + background: -o-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%); + background: -ms-linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%); + background: linear-gradient(top, #b4e391 0%,#61c419 50%,#b4e391 100%); + + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + + width: 0%; + height: 100%; +} + #PDFBug { font-size: 10px; position: fixed; diff --git a/web/viewer.html b/web/viewer.html index 34b2e77cb..d275f77c1 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -11,6 +11,7 @@ + @@ -142,7 +143,10 @@ -
Loading... 0%
+
+
Loading... 0%
+
+
diff --git a/web/viewer.js b/web/viewer.js index 389eee6ab..d133b09da 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -36,6 +36,48 @@ var Cache = function cacheCache(size) { }; }; +var ProgressBar = (function ProgressBarClosure() { + + function clamp(v, min, max) { + return Math.min(Math.max(v, min), max); + } + + function ProgressBar(id, opts) { + + // Fetch the sub-elements for later + this.div = document.querySelector(id + ' .progress'); + + // Get options, with sensible defaults + this.height = opts.height || 100; + this.width = opts.width || 100; + this.units = opts.units || '%'; + this.percent = opts.percent || 0; + + // Initialize heights + this.div.style.height = this.height + this.units; + } + + ProgressBar.prototype = { + + updateBar: function ProgressBar_updateBar() { + var progressSize = this.width * this._percent / 100; + + this.div.style.width = progressSize + this.units; + }, + + get percent() { + return this._percent; + }, + + set percent(val) { + this._percent = clamp(val, 0, 100); + this.updateBar(); + } + }; + + return ProgressBar; +})(); + var RenderingQueue = (function RenderingQueueClosure() { function RenderingQueue() { this.items = []; @@ -271,6 +313,10 @@ var PDFView = { document.title = getFileName(url) || url; + if (!PDFView.loadingBar) { + PDFView.loadingBar = new ProgressBar('#loadingBar', {}); + } + var self = this; PDFJS.getPdf( { @@ -411,6 +457,8 @@ var PDFView = { var percent = Math.round(level * 100); var loadingIndicator = document.getElementById('loading'); loadingIndicator.textContent = 'Loading... ' + percent + '%'; + + PDFView.loadingBar.percent = percent; }, load: function pdfViewLoad(data, scale) { @@ -425,8 +473,8 @@ var PDFView = { var errorWrapper = document.getElementById('errorWrapper'); errorWrapper.setAttribute('hidden', 'true'); - var loadingIndicator = document.getElementById('loading'); - loadingIndicator.setAttribute('hidden', 'true'); + var loadingBox = document.getElementById('loadingBox'); + loadingBox.setAttribute('hidden', 'true'); var sidebar = document.getElementById('sidebarView'); sidebar.parentNode.scrollTop = 0; @@ -510,6 +558,24 @@ var PDFView = { // Setting the default one. this.parseScale(kDefaultScale, true); } + + this.metadata = null; + var metadata = pdf.catalog.metadata; + var info = this.documentInfo = pdf.info; + var pdfTitle; + + if (metadata) { + this.metadata = metadata = new PDFJS.Metadata(metadata); + + if (metadata.has('dc:title')) + pdfTitle = metadata.get('dc:title'); + } + + if (!pdfTitle && info && info.has('Title')) + pdfTitle = info.get('Title'); + + if (pdfTitle) + document.title = pdfTitle; }, setHash: function pdfViewSetHash(hash) { @@ -1206,10 +1272,6 @@ window.addEventListener('load', function webViewerLoad(evt) { sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true); }, true); -window.addEventListener('unload', function webViewerUnload(evt) { - window.scrollTo(0, 0); -}, true); - /** * Render the next not yet visible page already such that it is * hopefully ready once the user scrolls to it.