diff --git a/LICENSE b/LICENSE index db52dec8e..f01ded412 100644 --- a/LICENSE +++ b/LICENSE @@ -10,6 +10,9 @@ Kalervo Kujala Adil Allawi <@ironymark> 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 34a7930a3..3cc423350 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,8 @@ PDF_JS_FILES = \ worker.js \ ../external/jpgjs/jpg.js \ jpx.js \ + bidi.js \ + metadata.js \ $(NULL) # make server @@ -59,7 +61,7 @@ test: shell-test browser-test production: | bundle @echo "Preparing web/viewer-production.html"; \ cd web; \ - sed '/PDFJSSCRIPT_REMOVE/d' viewer.html > viewer-1.tmp; \ + sed '/PDFJSSCRIPT_REMOVE_CORE/d' viewer.html > viewer-1.tmp; \ sed '/PDFJSSCRIPT_INCLUDE_BUILD/ r viewer-snippet.html' viewer-1.tmp > viewer-production.html; \ rm -f *.tmp; \ cd .. @@ -143,9 +145,9 @@ browser-test: # To install gjslint, see: # # -SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \ +SRC_DIRS := src utils web test examples/helloworld extensions/firefox \ extensions/firefox/components extensions/chrome test/unit -GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) +GJSLINT_FILES = $(foreach DIR, $(SRC_DIRS), $(wildcard $(DIR)/*.js)) lint: gjslint --nojsdoc $(GJSLINT_FILES) @@ -209,12 +211,14 @@ pages-repo: | $(BUILD_DIR) # copy of the pdf.js source. CONTENT_DIR := content BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'` -PDF_WEB_FILES = \ +PDFJSSCRIPT_VERSION := 0.2.$(BUILD_NUMBER) +EXTENSION_WEB_FILES = \ web/images \ - web/compatibility.js \ web/viewer.css \ web/viewer.js \ + web/viewer.html \ web/viewer-production.html \ + web/debugger.js \ $(NULL) FIREFOX_BUILD_DIR := $(BUILD_DIR)/firefox @@ -223,16 +227,28 @@ FIREFOX_CONTENT_DIR := $(EXTENSION_SRC)/firefox/$(CONTENT_DIR)/ FIREFOX_EXTENSION_FILES_TO_COPY = \ *.js \ *.rdf \ - chrome.manifest \ + *.png \ + install.rdf.in \ + README.mozilla \ components \ + ../../LICENSE \ $(NULL) FIREFOX_EXTENSION_FILES = \ - content \ - *.js \ + bootstrap.js \ install.rdf \ - chrome.manifest \ + icon.png \ + icon64.png \ components \ content \ + LICENSE \ + $(NULL) +FIREFOX_MC_EXTENSION_FILES = \ + bootstrap.js \ + icon.png \ + icon64.png \ + components \ + content \ + LICENSE \ $(NULL) CHROME_BUILD_DIR := $(BUILD_DIR)/chrome @@ -251,12 +267,26 @@ extension: | production @cd extensions/firefox; cp -r $(FIREFOX_EXTENSION_FILES_TO_COPY) ../../$(FIREFOX_BUILD_DIR)/ # Copy a standalone version of pdf.js inside the content directory @cp $(BUILD_TARGET) $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/ - @cp -r $(PDF_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ - @mv -f $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html $(FIREFOX_BUILD_CONTENT)/web/viewer.html + @cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ + @rm $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html + # Copy over the firefox extension snippet so we can inline pdf.js in it + @cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/ + # Modify the viewer so it does all the extension only stuff. + @cd $(FIREFOX_BUILD_CONTENT)/web; \ + sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \ + sed -i.bak '/PDFJSSCRIPT_REMOVE_CORE/d' viewer.html; \ + sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \ + sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \ + rm -f *.bak; + # We don't need pdf.js anymore since its inlined + @rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/; # Update the build version number - @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf - @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/install.rdf.in + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/update.rdf + @sed -i.bak "s/PDFJSSCRIPT_VERSION/$(PDFJSSCRIPT_VERSION)/" $(FIREFOX_BUILD_DIR)/README.mozilla @rm -f $(FIREFOX_BUILD_DIR)/*.bak + @find $(FIREFOX_BUILD_DIR) -name ".*" -delete # Create the xpi @cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES) @echo "extension created: " $(FIREFOX_EXTENSION_NAME) @@ -265,6 +295,8 @@ extension: | production @rm -f $(FIREFOX_BUILD_DIR)/*.bak @cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_AMO_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES) @echo "AMO extension created: " $(FIREFOX_AMO_EXTENSION_NAME) + # List all files for mozilla-central + @cd $(FIREFOX_BUILD_DIR); find $(FIREFOX_MC_EXTENSION_FILES) -type f > extension-files # Clear out everything in the chrome extension build directory @rm -Rf $(CHROME_BUILD_DIR) @@ -274,7 +306,7 @@ extension: | production @cp -R $(CHROME_EXTENSION_FILES) $(CHROME_BUILD_DIR)/ # Copy a standalone version of pdf.js inside the content directory @cp $(BUILD_TARGET) $(CHROME_BUILD_CONTENT)/$(BUILD_DIR)/ - @cp -r $(PDF_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/ + @cp -r $(EXTENSION_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/ @mv -f $(CHROME_BUILD_CONTENT)/web/viewer-production.html $(CHROME_BUILD_CONTENT)/web/viewer.html # Create the crx diff --git a/README.md b/README.md index f12fce934..4c6fc1e18 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ rendering PDFs, and eventually release a PDF reader extension powered by pdf.js. Integration with Firefox is a possibility if the experiment proves successful. - + # Getting started @@ -26,16 +26,19 @@ using the pdf.js API. ### Extension -A Firefox extension is also available: +A Firefox extension is availble in two places: -+ http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi ++ Stable Version: https://addons.mozilla.org/en-US/firefox/addon/pdfjs ++ Development Version: http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi -Note that this extension is self-updating, and by default Firefox will auto-update extensions on a -daily basis (you can change this through the `extensions.update.interval` option in `about:config`). +The development extension should be quite stable but still might break from time to time. +Also, note that the development extension is updated on every merge and by default Firefox will +auto-update extensions on a daily basis (you can change this through the +`extensions.update.interval` option in `about:config`). For an experimental Chrome extension, get the code as explained below and issue `make extension`. -Then open Chrome with the flag `--enable-experimental-extension-apis`, go to `Tools > Extension` -and load the (unpackaged) extension from the directory `extensions/chrome`. +Then open Chrome, go to `Tools > Extension` and load the (unpackaged) extension +from the directory `build/chrome`. ### Getting the code @@ -68,22 +71,16 @@ This will generate the file `build/pdf.js` that can be included in your final pr # Learning -Here are some initial pointers to help contributors get off the ground. -Additional resources are available in a separate section below. +You can play with the PDF.js API directly from your browser through the live demos below: -### Hello world ++ Hello world: http://jsbin.com/pdfjs-helloworld/edit#html,live ++ Simple reader with prev/next page controls: http://jsbin.com/pdfjs-prevnext/edit#html,live -For a "hello world" example, take a look at: +The repo contains a hello world example that you can run locally: + [examples/helloworld/hello.js](https://github.com/mozilla/pdf.js/blob/master/examples/helloworld/hello.js) -This example illustrates the bare minimum ingredients for integrating pdf.js -in a custom project. - -### Introductory video - -Check out the presentation by our contributor Julian Viereck on the inner -workings of PDF and pdf.js: +For an introduction to the PDF.js code, check out the presentation by our contributor Julian Viereck: + http://www.youtube.com/watch?v=Iv15UY-4Fg8 @@ -119,41 +116,14 @@ Our Github contributors so far: You can add your name to it! :) - # Running the tests -pdf.js comes with browser-level regression tests that allow one to probe +pdf.js comes with browser-level regression tests that allow one to probe whether it's able to successfully parse PDFs, as well as compare its output against reference images, pixel-by-pixel. -To run the tests, first configure the browser manifest file at: - - test/resources/browser_manifests/browser_manifest.json - -Sample manifests for different platforms are provided in that directory. - -To run all the bundled tests, type: - - $ make test - -and cross your fingers. Different types of tests are available, see the test -manifest file at: - - test/test_manifest.json - -The test type `eq` tests whether the output images are identical to reference -images. The test type `load` simply tests whether the file loads without -raising any errors. - - -### Running tests through our bot - -If you are a reviewer, you can use our remote bot to issue comprehensive tests -against reference images before merging pull requests. - -See the bot repo for details: - -+ https://github.com/mozilla/pdf.js-bot +More information about running the tests can be found on the +[contributor wiki page](https://github.com/mozilla/pdf.js/wiki/Contributing). # Additional resources diff --git a/extensions/chrome/manifest.json b/extensions/chrome/manifest.json index 629e41b31..9b34437b2 100644 --- a/extensions/chrome/manifest.json +++ b/extensions/chrome/manifest.json @@ -3,7 +3,7 @@ "version": "0.1", "description": "Read PDF Document", "permissions": [ - "experimental", + "webRequest", "webRequestBlocking", "http://*/*.pdf", "file:///*/*.pdf" ], diff --git a/extensions/chrome/pdfHandler.html b/extensions/chrome/pdfHandler.html index c13e24c57..fc67c581d 100644 --- a/extensions/chrome/pdfHandler.html +++ b/extensions/chrome/pdfHandler.html @@ -1,18 +1,28 @@ diff --git a/extensions/firefox/README.mozilla b/extensions/firefox/README.mozilla new file mode 100644 index 000000000..a7be57a63 --- /dev/null +++ b/extensions/firefox/README.mozilla @@ -0,0 +1,4 @@ +This is the pdf.js project output, https://github.com/mozilla/pdf.js + +Current extension version is: PDFJSSCRIPT_VERSION + diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index e51df28f8..d03812bcb 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -3,44 +3,96 @@ 'use strict'; +const RESOURCE_NAME = 'pdf.js'; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; + let Cc = Components.classes; let Ci = Components.interfaces; let Cm = Components.manager; let Cu = Components.utils; +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); Cu.import('resource://gre/modules/Services.jsm'); function log(str) { + if (!application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false)) + return; dump(str + '\n'); } -function startup(aData, aReason) { - let manifestPath = 'chrome.manifest'; - let manifest = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - try { - manifest.initWithPath(aData.installPath.path); - manifest.append(manifestPath); - Cm.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest); - Services.prefs.setBoolPref('extensions.pdf.js.active', true); - } catch (e) { - log(e); +// Register/unregister a class as a component. +let Factory = { + registrar: null, + aClass: null, + register: function(aClass) { + if (this.aClass) { + log('Cannot register more than one class'); + return; + } + this.registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + this.aClass = aClass; + var proto = aClass.prototype; + this.registrar.registerFactory(proto.classID, proto.classDescription, + proto.contractID, this); + }, + unregister: function() { + if (!this.aClass) { + log('Class was never registered.'); + return; + } + var proto = this.aClass.prototype; + this.registrar.unregisterFactory(proto.classID, this); + this.aClass = null; + }, + // nsIFactory::createInstance + createInstance: function(outer, iid) { + if (outer !== null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return (new (this.aClass)).QueryInterface(iid); } +}; + +let pdfStreamConverterUrl = null; + +// As of Firefox 13 bootstrapped add-ons don't support automatic registering and +// unregistering of resource urls and components/contracts. Until then we do +// it programatically. See ManifestDirective ManifestParser.cpp for support. + +function startup(aData, aReason) { + // Setup the resource url. + var ioService = Services.io; + var resProt = ioService.getProtocolHandler('resource') + .QueryInterface(Ci.nsIResProtocolHandler); + var aliasURI = ioService.newURI('content/', 'UTF-8', aData.resourceURI); + resProt.setSubstitution(RESOURCE_NAME, aliasURI); + + // Load the component and register it. + pdfStreamConverterUrl = aData.resourceURI.spec + + 'components/PdfStreamConverter.js'; + Cu.import(pdfStreamConverterUrl); + Factory.register(PdfStreamConverter); } function shutdown(aData, aReason) { - if (Services.prefs.getBoolPref('extensions.pdf.js.active')) - Services.prefs.setBoolPref('extensions.pdf.js.active', false); + if (aReason == APP_SHUTDOWN) + return; + var ioService = Services.io; + var resProt = ioService.getProtocolHandler('resource') + .QueryInterface(Ci.nsIResProtocolHandler); + // Remove the resource url. + resProt.setSubstitution(RESOURCE_NAME, null); + // Remove the contract/component. + Factory.unregister(); + // Unload the converter + Cu.unload(pdfStreamConverterUrl); + pdfStreamConverterUrl = null; } function install(aData, aReason) { - let url = 'chrome://pdf.js/content/web/viewer.html?file=%s'; - Services.prefs.setCharPref('extensions.pdf.js.url', url); - Services.prefs.setBoolPref('extensions.pdf.js.active', false); } function uninstall(aData, aReason) { - Services.prefs.clearUserPref('extensions.pdf.js.url'); - Services.prefs.clearUserPref('extensions.pdf.js.active'); + application.prefs.setValue(EXT_PREFIX + '.database', '{}'); } diff --git a/extensions/firefox/chrome.manifest b/extensions/firefox/chrome.manifest deleted file mode 100644 index d7db20b38..000000000 --- a/extensions/firefox/chrome.manifest +++ /dev/null @@ -1,5 +0,0 @@ -content pdf.js content/ - -component {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} components/pdfContentHandler.js -contract @mozilla.org/uriloader/content-handler;1?type=application/pdf {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} - diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js new file mode 100644 index 000000000..4467abc6b --- /dev/null +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -0,0 +1,189 @@ +/* -*- 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 EXPORTED_SYMBOLS = ['PdfStreamConverter']; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; +const PDFJS_EVENT_ID = 'pdf.js.message'; +const PDF_CONTENT_TYPE = 'application/pdf'; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; +const MAX_DATABASE_LENGTH = 4096; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); +let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + +function log(aMsg) { + if (!application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false)) + return; + let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); + Services.console.logStringMessage(msg); + dump(msg + '\n'); +} + +function getDOMWindow(aChannel) { + var requestor = aChannel.notificationCallbacks; + var win = requestor.getInterface(Components.interfaces.nsIDOMWindow); + return win; +} + +// All the priviledged actions. +function ChromeActions() { + this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; +} +ChromeActions.prototype = { + download: function(data) { + Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); + }, + setDatabase: function(data) { + if (this.inPrivateBrowswing) + return; + // Protect against something sending tons of data to setDatabase. + if (data.length > MAX_DATABASE_LENGTH) + return; + application.prefs.setValue(EXT_PREFIX + '.database', data); + }, + getDatabase: function() { + if (this.inPrivateBrowswing) + return '{}'; + return application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + }, + pdfBugEnabled: function() { + return application.prefs.getValue(EXT_PREFIX + '.pdfBugEnabled', false); + } +}; + + +// Event listener to trigger chrome privedged code. +function RequestListener(actions) { + this.actions = actions; +} +// Receive an event and synchronously responds. +RequestListener.prototype.receive = function(event) { + var message = event.target; + var action = message.getUserData('action'); + var data = message.getUserData('data'); + var actions = this.actions; + if (!(action in actions)) { + log('Unknown action: ' + action); + return; + } + var response = actions[action].call(this.actions, data); + message.setUserData('response', response, null); +}; + + +function PdfStreamConverter() { +} + +PdfStreamConverter.prototype = { + + // properties required for XPCOM registration: + classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'), + classDescription: 'pdf.js Component', + contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsISupports, + Ci.nsIStreamConverter, + Ci.nsIStreamListener, + Ci.nsIRequestObserver + ]), + + /* + * This component works as such: + * 1. asyncConvertData stores the listener + * 2. onStartRequest creates a new channel, streams the viewer and cancels + * the request so pdf.js can do the request + * Since the request is cancelled onDataAvailable should not be called. The + * onStopRequest does nothing. The convert function just returns the stream, + * it's just the synchronous version of asyncConvertData. + */ + + // nsIStreamConverter::convert + convert: function(aFromStream, aFromType, aToType, aCtxt) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + // nsIStreamConverter::asyncConvertData + asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { + // Ignoring HTTP POST requests -- pdf.js has to repeat the request. + var skipConversion = false; + try { + var request = aCtxt; + request.QueryInterface(Ci.nsIHttpChannel); + skipConversion = (request.requestMethod !== 'GET'); + } catch (e) { + // Non-HTTP request... continue normally. + } + if (skipConversion) + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + + // Store the listener passed to us + this.listener = aListener; + }, + + // nsIStreamListener::onDataAvailable + onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { + // Do nothing since all the data loading is handled by the viewer. + log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!'); + }, + + // nsIRequestObserver::onStartRequest + onStartRequest: function(aRequest, aContext) { + + // Setup the request so we can use it below. + aRequest.QueryInterface(Ci.nsIChannel); + // Cancel the request so the viewer can handle it. + aRequest.cancel(Cr.NS_BINDING_ABORTED); + + // Create a new channel that is viewer loaded as a resource. + var ioService = Services.io; + 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(proxy, aContext); + }, + + // nsIRequestObserver::onStopRequest + onStopRequest: function(aRequest, aContext, aStatusCode) { + // Do nothing. + } +}; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]); diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js deleted file mode 100644 index 67459b759..000000000 --- a/extensions/firefox/components/pdfContentHandler.js +++ /dev/null @@ -1,67 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -const PDF_CONTENT_TYPE = 'application/pdf'; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://gre/modules/Services.jsm'); - -function log(aMsg) { - let msg = 'pdfContentHandler.js: ' + (aMsg.join ? aMsg.join('') : aMsg); - Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService) - .logStringMessage(msg); - dump(msg + '\n'); -} - -const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001; -function pdfContentHandler() { -} - -pdfContentHandler.prototype = { - handleContent: function handleContent(aMimetype, aContext, aRequest) { - if (aMimetype != PDF_CONTENT_TYPE) - throw NS_ERROR_WONT_HANDLE_CONTENT; - - if (!(aRequest instanceof Ci.nsIChannel)) - throw NS_ERROR_WONT_HANDLE_CONTENT; - - if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) - throw NS_ERROR_WONT_HANDLE_CONTENT; - - let window = null; - let callbacks = aRequest.notificationCallbacks || - aRequest.loadGroup.notificationCallbacks; - if (!callbacks) - return; - - window = callbacks.getInterface(Ci.nsIDOMWindow); - - let url = null; - try { - url = Services.prefs.getCharPref('extensions.pdf.js.url'); - } catch (e) { - log('Error retrieving the pdf.js base url - ' + e); - throw NS_ERROR_WONT_HANDLE_CONTENT; - } - - let targetUrl = aRequest.URI.spec; - if (targetUrl.indexOf('#pdfjs.action=download') >= 0) - throw NS_ERROR_WONT_HANDLE_CONTENT; - - aRequest.cancel(Cr.NS_BINDING_ABORTED); - window.location = url.replace('%s', encodeURIComponent(targetUrl)); - }, - - classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]) -}; - -var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]); - diff --git a/extensions/firefox/icon.png b/extensions/firefox/icon.png new file mode 100644 index 000000000..64763756e Binary files /dev/null and b/extensions/firefox/icon.png differ diff --git a/extensions/firefox/icon64.png b/extensions/firefox/icon64.png new file mode 100644 index 000000000..0131bbb5c Binary files /dev/null and b/extensions/firefox/icon64.png differ diff --git a/extensions/firefox/install.rdf b/extensions/firefox/install.rdf index f4e6b43ea..0a0d813b2 100644 --- a/extensions/firefox/install.rdf +++ b/extensions/firefox/install.rdf @@ -5,20 +5,18 @@ uriloader@pdf.js - pdf.js - 0.2.PDFJSSCRIPT_BUILD - chrome://pdf.js/skin/logo.png + PDF Viewer + PDFJSSCRIPT_VERSION {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 6.0 - 11.0a1 + 14.0a1 true - true - Mozilla Labs - pdf.js uri loader + Mozilla + Uses HTML5 to display PDF files directly in Firefox. https://github.com/mozilla/pdf.js/ 2 diff --git a/extensions/firefox/install.rdf.in b/extensions/firefox/install.rdf.in new file mode 100644 index 000000000..feab3e7ad --- /dev/null +++ b/extensions/firefox/install.rdf.in @@ -0,0 +1,26 @@ + + +#filter substitution + + + + + uriloader@pdf.js + PDF Viewer + PDFJSSCRIPT_VERSION + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + @FIREFOX_VERSION@ + @FIREFOX_VERSION@ + + + true + true + Mozilla + Uses HTML5 to display PDF files directly in Firefox. + https://support.mozilla.org/kb/Opening%20PDF%20files%20within%20Firefox + 2 + + diff --git a/extensions/firefox/update.rdf b/extensions/firefox/update.rdf index c12e53dc6..b31051b5f 100644 --- a/extensions/firefox/update.rdf +++ b/extensions/firefox/update.rdf @@ -8,7 +8,7 @@ - 0.2.PDFJSSCRIPT_BUILD + PDFJSSCRIPT_VERSION {ec8030f7-c20a-464f-9b0e-13a3a9e97384} diff --git a/external/shelljs/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/LICENSE b/external/shelljs/LICENSE new file mode 100644 index 000000000..1b35ee9fb --- /dev/null +++ b/external/shelljs/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2012, Artur Adib +All rights reserved. + +You may use this project under the terms of the New BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Artur Adib nor the + names of the contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/shelljs/README.md b/external/shelljs/README.md new file mode 100644 index 000000000..af5f05127 --- /dev/null +++ b/external/shelljs/README.md @@ -0,0 +1,370 @@ +# 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._ ++ _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. + +The project is [unit-tested](http://travis-ci.org/arturadib/shelljs) and is being used at Mozilla's [pdf.js](http://github.com/mozilla/pdf.js). + + +### Example + +```javascript +require('shelljs/global'); + +// Copy files to release dir +mkdir('-p', 'out/Release'); +cp('-R', 'stuff/*', 'out/Release'); + +// Replace macros in each .js file +cd('lib'); +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 +if (exec('git commit -am "Auto-commit"').code !== 0) { + echo('Error: Git commit failed'); + exit(1); +} +``` + +### Global vs. Local + +The example above uses the convenience script `shelljs/global` to reduce verbosity. If polluting your global namespace is not desirable, simply require `shelljs`. + +Example: + +```javascript +var shell = require('shelljs'); +shell.echo('hello world'); +``` + +### Make tool + +A convenience script `shelljs/make` is also provided to mimic the behavior of a Unix Makefile. In this case all shell objects are global, and command line arguments will cause the script to execute only the corresponding function in the global `target` object. To avoid redundant calls, target functions are executed only once per script. + +Example: + +```javascript +// +// Example file: make.js +// +require('shelljs/make'); + +target.all = function() { + target.bundle(); + target.docs(); +} + +// Bundle JS files +target.bundle = function() { + cd(__dirname); + mkdir('build'); + cd('lib'); + cat('*.js').to('../build/output.js'); +} + +// Generate docs +target.docs = function() { + cd(__dirname); + mkdir('docs'); + cd('lib'); + ls('*.js').forEach(function(file){ + var text = grep('//@', file); // extract special comments + text.replace('//@', ''); // remove comment tags + text.to('docs/my_docs.md'); + }); +} +``` + +To run the target `all`, call the above script without arguments: `$ node make`. To run the target `docs`: `$ node make docs`, and so on. + +### Installing + +Via npm: + +```bash +$ npm install shelljs +``` + +Or simply copy `shell.js` into your project's directory, and `require()` accordingly. + + + + + +# Command reference + + +All commands run synchronously, unless otherwise stated. + + +#### cd('dir') +Changes to directory `dir` for the duration of the script + +#### pwd() +Returns the current directory. + +#### ls([options ,] path [,path ...]) +#### ls([options ,] path_array) +Available options: + ++ `-R`: recursive ++ `-a`: all files (include files beginning with `.`) + +Examples: + +```javascript +ls('projs/*.js'); +ls('-R', '/users/me', '/tmp'); +ls('-R', ['/users/me', '/tmp']); // same as above +``` + +Returns array of files in the given path, or in current directory if no path provided. + +#### find(path [,path ...]) +#### find(path_array) +Examples: + +```javascript +find('src', 'lib'); +find(['src', 'lib']); // same as above +find('.').filter(function(file) { return file.match(/\.js$/); }); +``` + +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`. + +#### cp('[options ,] source [,source ...], dest') +#### cp('[options ,] source_array, dest') +Available options: + ++ `-f`: force ++ `-r, -R`: recursive + +Examples: + +```javascript +cp('file1', 'dir1'); +cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp'); +cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above +``` + +Copies files. The wildcard `*` is accepted. + +#### rm([options ,] file [, file ...]) +#### rm([options ,] file_array) +Available options: + ++ `-f`: force ++ `-r, -R`: recursive + +Examples: + +```javascript +rm('-rf', '/tmp/*'); +rm('some_file.txt', 'another_file.txt'); +rm(['some_file.txt', 'another_file.txt']); // same as above +``` + +Removes files. The wildcard `*` is accepted. + +#### mv(source [, source ...], dest') +#### mv(source_array, dest') +Available options: + ++ `f`: force + +Examples: + +```javascript +mv('-f', 'file', 'dir/'); +mv('file1', 'file2', 'dir/'); +mv(['file1', 'file2'], 'dir/'); // same as above +``` + +Moves files. The wildcard `*` is accepted. + +#### mkdir([options ,] dir [, dir ...]) +#### mkdir([options ,] dir_array) +Available options: + ++ `p`: full path (will create intermediate dirs if necessary) + +Examples: + +```javascript +mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g'); +mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above +``` + +Creates directories. + +#### test(expression) +Available expression primaries: + ++ `'-d', 'path'`: true if path is a directory ++ `'-f', 'path'`: true if path is a regular file + +Examples: + +```javascript +if (test('-d', path)) { /* do something with dir */ }; +if (!test('-f', path)) continue; // skip if it's a regular file +``` + +Evaluates expression using the available primaries and returns corresponding value. + +#### cat(file [, file ...]) +#### cat(file_array) + +Examples: + +```javascript +var str = cat('file*.txt'); +var str = cat('file1', 'file2'); +var str = cat(['file1', 'file2']); // same as above +``` + +Returns a string containing the given file, or a concatenated string +containing the files if more than one file is given (a new line character is +introduced between each file). Wildcard `*` accepted. + +#### 'string'.to(file) + +Examples: + +```javascript +cat('input.txt').to('output.txt'); +``` + +Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as +those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_ + +#### sed([options ,] search_regex, replace_str, file) +Available options: + ++ `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_ + +Examples: + +```javascript +sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js'); +sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js'); +``` + +Reads an input string from `file` and performs a JavaScript `replace()` on the input +using the given search regex and replacement string. Returns the new string after replacement. + +#### grep(regex_filter, file [, file ...]) +#### grep(regex_filter, file_array) + +Examples: + +```javascript +grep('GLOBAL_VARIABLE', '*.js'); +``` + +Reads input string from given files and returns a string containing all lines of the +file that match the given `regex_filter`. Wildcard `*` accepted. + +#### which(command) + +Examples: + +```javascript +var nodeExec = which('node'); +``` + +Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions. +Returns string containing the absolute path to the command. + +#### echo(string [,string ...]) + +Examples: + +```javascript +echo('hello world'); +var str = echo('hello world'); +``` + +Prints string to stdout, and returns string with additional utility methods +like `.to()`. + +#### exit(code) +Exits the current process with the given exit code. + +#### env['VAR_NAME'] +Object containing environment variables (both getter and setter). Shortcut to process.env. + +#### exec(command [, options] [, callback]) +Available options (all `false` by default): + ++ `async`: Asynchronous execution. Needs callback. ++ `silent`: Do not echo program output to console. + +Examples: + +```javascript +var version = exec('node --version', {silent:true}).output; +``` + +Executes the given `command` _synchronously_, unless otherwise specified. +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. + +## Non-Unix commands + + +#### tempdir() +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). + +#### error() +Tests if error occurred in the last command. Returns `null` if no error occurred, +otherwise returns string explaining the 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. + +## 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/global.js b/external/shelljs/global.js new file mode 100644 index 000000000..83a27e69a --- /dev/null +++ b/external/shelljs/global.js @@ -0,0 +1,3 @@ +var shell = require('./shell.js'); +for (cmd in shell) + global[cmd] = shell[cmd]; diff --git a/external/shelljs/make.js b/external/shelljs/make.js new file mode 100644 index 000000000..c495735bf --- /dev/null +++ b/external/shelljs/make.js @@ -0,0 +1,46 @@ +require('./global'); + +global.target = {}; + +// This ensures we only execute the script targets after the entire script has +// been evaluated +var args = process.argv.slice(2); +setTimeout(function() { + + if (args.length === 1 && args[0] === '--help') { + console.log('Available targets:'); + for (t in target) + console.log(' ' + t); + return; + } + + // Wrap targets to prevent duplicate execution + for (t in target) { + (function(t, oldTarget){ + + // Wrap it + target[t] = function(force) { + if (oldTarget.done && !force) + return; + oldTarget.done = true; + return oldTarget.apply(oldTarget, arguments); + } + + })(t, target[t]); + } + + // Execute desired targets + if (args.length > 0) { + args.forEach(function(arg) { + if (arg in target) + target[arg](); + else { + console.log('no such target: ' + arg); + exit(1); + } + }); + } else if ('all' in target) { + target.all(); + } + +}, 0); diff --git a/external/shelljs/package.json b/external/shelljs/package.json new file mode 100644 index 000000000..4d2830cb9 --- /dev/null +++ b/external/shelljs/package.json @@ -0,0 +1,29 @@ +{ + "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 new file mode 100644 index 000000000..92c49c54d --- /dev/null +++ b/external/shelljs/shell.js @@ -0,0 +1,1421 @@ +// +// ShellJS +// Unix shell commands on top of Node's API +// +// Copyright (c) 2012 Artur Adib +// http://github.com/arturadib/shelljs +// + +var fs = require('fs'), + path = require('path'), + util = require('util'), + vm = require('vm'), + child = require('child_process'), + os = require('os'); + +// Node shims for < v0.7 +fs.existsSync = fs.existsSync || path.existsSync; + +var state = { + error: null, + fatal: false, + silent: false, + currentCmd: 'shell.js', + tempDir: null + }, + platform = os.type().match(/^Win/) ? 'win' : 'unix'; + + +//@ +//@ All commands run synchronously, unless otherwise stated. +//@ + + +//@ +//@ #### cd('dir') +//@ Changes to directory `dir` for the duration of the script +function _cd(options, dir) { + if (!dir) + error('directory not specified'); + + if (!fs.existsSync(dir)) + error('no such file or directory: ' + dir); + + if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory()) + error('not a directory: ' + dir); + + process.chdir(dir); +}; +exports.cd = wrap('cd', _cd); + +//@ +//@ #### pwd() +//@ Returns the current directory. +function _pwd(options) { + var pwd = path.resolve(process.cwd()); + return ShellString(pwd); +}; +exports.pwd = wrap('pwd', _pwd); + + +//@ +//@ #### ls([options ,] path [,path ...]) +//@ #### ls([options ,] path_array) +//@ Available options: +//@ +//@ + `-R`: recursive +//@ + `-a`: all files (include files beginning with `.`) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ ls('projs/*.js'); +//@ ls('-R', '/users/me', '/tmp'); +//@ ls('-R', ['/users/me', '/tmp']); // same as above +//@ ``` +//@ +//@ 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', + 'a': 'all' + }); + + if (!paths) + paths = ['.']; + else if (typeof paths === 'object') + paths = paths; // assume array + else if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + + var list = []; + + // 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 false; + } + + 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()) { + pushFile(p, p); + return; // continue + } + + // Simple dir? + if (fs.statSync(p).isDirectory()) { + // Iterate over p contents + fs.readdirSync(p).forEach(function(file) { + if (!pushFile(file, p)) + return; + + // 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 + } + } + + // p does not exist - possible wildcard present + + var basename = path.basename(p); + var dirname = path.dirname(p); + // Wildcard present on an existing dir? (e.g. '/tmp/*.js') + if (basename.search(/\*/) > -1 && fs.existsSync(dirname) && fs.statSync(dirname).isDirectory) { + // Escape special regular expression chars + var regexp = basename.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\.|\+|\?)/g, '\\$1'); + // Translates wildcard into regex + regexp = '^' + regexp.replace(/\*/g, '.*') + '$'; + // Iterate over directory contents + fs.readdirSync(dirname).forEach(function(file) { + if (file.match(new RegExp(regexp))) { + if (!pushFile(path.normalize(dirname+'/'+file), basename)) + return; + + // 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; + } + + error('no such file or directory: ' + p, true); + }); + + return list; +}; +exports.ls = wrap('ls', _ls); + + +//@ +//@ #### find(path [,path ...]) +//@ #### find(path_array) +//@ Examples: +//@ +//@ ```javascript +//@ find('src', 'lib'); +//@ find(['src', 'lib']); // same as above +//@ find('.').filter(function(file) { return file.match(/\.js$/); }); +//@ ``` +//@ +//@ 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`. +function _find(options, paths) { + if (!paths) + error('no path specified'); + else if (typeof paths === 'object') + paths = paths; // assume array + else if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + + var 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) { + pushFile(file); + + if (fs.statSync(file).isDirectory()) { + _ls('-Ra', file+'/*').forEach(function(subfile) { + pushFile(subfile); + }); + } + }); + + return list; +} +exports.find = wrap('find', _find); + + +//@ +//@ #### cp('[options ,] source [,source ...], dest') +//@ #### cp('[options ,] source_array, dest') +//@ Available options: +//@ +//@ + `-f`: force +//@ + `-r, -R`: recursive +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cp('file1', 'dir1'); +//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp'); +//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above +//@ ``` +//@ +//@ Copies files. The wildcard `*` is accepted. +function _cp(options, sources, dest) { + options = parseOptions(options, { + 'f': 'force', + 'R': 'recursive', + 'r': 'recursive' + }); + + // Get sources, dest + if (arguments.length < 3) { + error('missing and/or '); + } else if (arguments.length > 3) { + sources = [].slice.call(arguments, 1, arguments.length - 1); + dest = arguments[arguments.length - 1]; + } else if (typeof sources === 'string') { + sources = [sources]; + } else if ('length' in sources) { + sources = sources; // no-op for array + } else { + error('invalid arguments'); + } + + // Dest is not existing dir, but multiple sources given + if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1) + error('dest is not a directory (too many sources)'); + + // Dest is an existing file, but no -f given + if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force) + error('dest file already exists: ' + dest); + + sources = expand(sources); + + sources.forEach(function(src) { + if (!fs.existsSync(src)) { + error('no such file or directory: '+src, true); + return; // skip file + } + + // If here, src exists + + if (fs.statSync(src).isDirectory()) { + if (!options.recursive) { + // Non-Recursive + log(src + ' is a directory (not copied)'); + } else { + // Recursive + // 'cp /a/source dest' should create 'source' in 'dest' + var newDest = dest+'/'+path.basename(src), + checkDir = fs.statSync(src); + try { + fs.mkdirSync(newDest, checkDir.mode); + } catch (e) { + //if the directory already exists, that's okay + if (e.code !== 'EEXIST') throw e; + } + cpdirSyncRecursive(src, newDest, {force: options.force}); + } + return; // done with dir + } + + // If here, src is a file + + // When copying to '/path/dir': + // thisDest = '/path/dir/file1' + var thisDest = dest; + if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) + thisDest = path.normalize(dest + '/' + path.basename(src)); + + if (fs.existsSync(thisDest) && !options.force) { + error('dest file already exists: ' + thisDest, true); + return; // skip file + } + + copyFileSync(src, thisDest); + }); // forEach(src) +}; // cp +exports.cp = wrap('cp', _cp); + +//@ +//@ #### rm([options ,] file [, file ...]) +//@ #### rm([options ,] file_array) +//@ Available options: +//@ +//@ + `-f`: force +//@ + `-r, -R`: recursive +//@ +//@ Examples: +//@ +//@ ```javascript +//@ rm('-rf', '/tmp/*'); +//@ rm('some_file.txt', 'another_file.txt'); +//@ rm(['some_file.txt', 'another_file.txt']); // same as above +//@ ``` +//@ +//@ Removes files. The wildcard `*` is accepted. +function _rm(options, files) { + options = parseOptions(options, { + 'f': 'force', + 'r': 'recursive', + 'R': 'recursive' + }); + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 1); + // if it's array leave it as it is + + files = expand(files); + + files.forEach(function(file) { + if (!fs.existsSync(file)) { + // Path does not exist, no force flag given + if (!options.force) + error('no such file or directory: '+file, true); + + return; // skip file + } + + // If here, path exists + + // Remove simple file + if (fs.statSync(file).isFile()) { + + // 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) { + error('path is a directory', true); + return; // skip path + } + + // Recursively remove existing directory + if (fs.statSync(file).isDirectory() && options.recursive) { + rmdirSyncRecursive(file, options.force); + } + }); // forEach(file) +}; // rm +exports.rm = wrap('rm', _rm); + +//@ +//@ #### mv(source [, source ...], dest') +//@ #### mv(source_array, dest') +//@ Available options: +//@ +//@ + `f`: force +//@ +//@ Examples: +//@ +//@ ```javascript +//@ mv('-f', 'file', 'dir/'); +//@ mv('file1', 'file2', 'dir/'); +//@ mv(['file1', 'file2'], 'dir/'); // same as above +//@ ``` +//@ +//@ Moves files. The wildcard `*` is accepted. +function _mv(options, sources, dest) { + options = parseOptions(options, { + 'f': 'force' + }); + + // Get sources, dest + if (arguments.length < 3) { + error('missing and/or '); + } else if (arguments.length > 3) { + sources = [].slice.call(arguments, 1, arguments.length - 1); + dest = arguments[arguments.length - 1]; + } else if (typeof sources === 'string') { + sources = [sources]; + } else if ('length' in sources) { + sources = sources; // no-op for array + } else { + error('invalid arguments'); + } + + sources = expand(sources); + + // Dest is not existing dir, but multiple sources given + if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1) + error('dest is not a directory (too many sources)'); + + // Dest is an existing file, but no -f given + if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force) + error('dest file already exists: ' + dest); + + sources.forEach(function(src) { + if (!fs.existsSync(src)) { + error('no such file or directory: '+src, true); + return; // skip file + } + + // If here, src exists + + // When copying to '/path/dir': + // thisDest = '/path/dir/file1' + var thisDest = dest; + if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) + thisDest = path.normalize(dest + '/' + path.basename(src)); + + if (fs.existsSync(thisDest) && !options.force) { + error('dest file already exists: ' + thisDest, true); + return; // skip file + } + + if (path.resolve(src) === path.dirname(path.resolve(thisDest))) { + error('cannot move to self: '+src, true); + return; // skip file + } + + fs.renameSync(src, thisDest); + }); // forEach(src) +}; // mv +exports.mv = wrap('mv', _mv); + +//@ +//@ #### mkdir([options ,] dir [, dir ...]) +//@ #### mkdir([options ,] dir_array) +//@ Available options: +//@ +//@ + `p`: full path (will create intermediate dirs if necessary) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g'); +//@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above +//@ ``` +//@ +//@ Creates directories. +function _mkdir(options, dirs) { + options = parseOptions(options, { + 'p': 'fullpath' + }); + if (!dirs) + error('no paths given'); + + if (typeof dirs === 'string') + dirs = [].slice.call(arguments, 1); + // if it's array leave it as it is + + dirs.forEach(function(dir) { + if (fs.existsSync(dir)) { + if (!options.fullpath) + error('path already exists: ' + dir, true); + return; // skip dir + } + + // Base dir does not exist, and no -p option given + var baseDir = path.dirname(dir); + if (!fs.existsSync(baseDir) && !options.fullpath) { + error('no such file or directory: ' + baseDir, true); + return; // skip dir + } + + if (options.fullpath) + mkdirSyncRecursive(dir); + else + fs.mkdirSync(dir, 0777); + }); +}; // mkdir +exports.mkdir = wrap('mkdir', _mkdir); + +//@ +//@ #### test(expression) +//@ Available expression primaries: +//@ +//@ + `'-d', 'path'`: true if path is a directory +//@ + `'-f', 'path'`: true if path is a regular file +//@ +//@ Examples: +//@ +//@ ```javascript +//@ if (test('-d', path)) { /* do something with dir */ }; +//@ if (!test('-f', path)) continue; // skip if it's a regular file +//@ ``` +//@ +//@ Evaluates expression using the available primaries and returns corresponding value. +function _test(options, path) { + if (!path) + error('no path given'); + + // hack - only works with unary primaries + options = parseOptions(options, { + 'd': 'directory', + 'f': 'file' + }); + if (!options.directory && !options.file) + error('could not interpret expression'); + + if (options.directory) + return fs.existsSync(path) && fs.statSync(path).isDirectory(); + + if (options.file) + return fs.existsSync(path) && fs.statSync(path).isFile(); +}; // test +exports.test = wrap('test', _test); + + +//@ +//@ #### cat(file [, file ...]) +//@ #### cat(file_array) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var str = cat('file*.txt'); +//@ var str = cat('file1', 'file2'); +//@ var str = cat(['file1', 'file2']); // same as above +//@ ``` +//@ +//@ Returns a string containing the given file, or a concatenated string +//@ containing the files if more than one file is given (a new line character is +//@ introduced between each file). Wildcard `*` accepted. +function _cat(options, files) { + var cat = ''; + + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 1); + // if it's array leave it as it is + + files = expand(files); + + files.forEach(function(file) { + if (!fs.existsSync(file)) + error('no such file or directory: ' + file); + + cat += fs.readFileSync(file, 'utf8') + '\n'; + }); + + if (cat[cat.length-1] === '\n') + cat = cat.substring(0, cat.length-1); + + return ShellString(cat); +}; +exports.cat = wrap('cat', _cat); + +//@ +//@ #### 'string'.to(file) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cat('input.txt').to('output.txt'); +//@ ``` +//@ +//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as +//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_ +function _to(options, file) { + if (!file) + error('wrong arguments'); + + if (!fs.existsSync( path.dirname(file) )) + error('no such file or directory: ' + path.dirname(file)); + + 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 +function ShellString(str) { + return str; +} +String.prototype.to = wrap('to', _to); + +//@ +//@ #### sed([options ,] search_regex, replace_str, file) +//@ Available options: +//@ +//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_ +//@ +//@ Examples: +//@ +//@ ```javascript +//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js'); +//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js'); +//@ ``` +//@ +//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input +//@ using the given search regex and replacement string. Returns the new string after replacement. +function _sed(options, regex, replacement, file) { + options = parseOptions(options, { + 'i': 'inplace' + }); + + if (typeof replacement === 'string') + replacement = replacement; // no-op + else if (typeof replacement === 'number') + replacement = replacement.toString(); // fallback + else + error('invalid replacement string'); + + if (!file) + error('no file given'); + + if (!fs.existsSync(file)) + error('no such file or directory: ' + file); + + var result = fs.readFileSync(file, 'utf8').replace(regex, replacement); + if (options.inplace) + fs.writeFileSync(file, result, 'utf8'); + + return ShellString(result); +}; +exports.sed = wrap('sed', _sed); + +//@ +//@ #### grep(regex_filter, file [, file ...]) +//@ #### grep(regex_filter, file_array) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ grep('GLOBAL_VARIABLE', '*.js'); +//@ ``` +//@ +//@ Reads input string from given files and returns a string containing all lines of the +//@ file that match the given `regex_filter`. Wildcard `*` accepted. +function _grep(options, regex, files) { + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 2); + // if it's array leave it as it is + + files = expand(files); + + var grep = ''; + files.forEach(function(file) { + if (!fs.existsSync(file)) { + error('no such file or directory: ' + file, true); + return; + } + + var contents = fs.readFileSync(file, 'utf8'), + lines = contents.split(/\r*\n/); + lines.forEach(function(line) { + if (line.match(regex)) + grep += line + '\n'; + }); + }); + + return ShellString(grep); +}; +exports.grep = wrap('grep', _grep); + + +//@ +//@ #### which(command) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var nodeExec = which('node'); +//@ ``` +//@ +//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions. +//@ Returns string containing the absolute path to the command. +function _which(options, cmd) { + if (!cmd) + error('must specify command'); + + var pathEnv = process.env.path || process.env.Path || process.env.PATH, + pathArray = splitPath(pathEnv), + where = null; + + // No relative/absolute paths provided? + if (cmd.search(/\//) === -1) { + // Search for command in PATH + pathArray.forEach(function(dir) { + if (where) + return; // already found it + + var attempt = path.resolve(dir + '/' + cmd); + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + + if (platform === 'win') { + var baseAttempt = attempt; + attempt = baseAttempt + '.exe'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.cmd'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.bat'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + } // if 'win' + }); + } + + // Command not found anywhere? + if (!fs.existsSync(cmd) && !where) + return null; + + where = where || path.resolve(cmd); + + return ShellString(where); +}; +exports.which = wrap('which', _which); + +//@ +//@ #### echo(string [,string ...]) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ echo('hello world'); +//@ var str = echo('hello world'); +//@ ``` +//@ +//@ Prints string to stdout, and returns string with additional utility methods +//@ like `.to()`. +function _echo(options) { + var messages = [].slice.call(arguments, 1); + console.log.apply(this, messages); + return ShellString(messages.join(' ')); +}; +exports.echo = wrap('echo', _echo); + +//@ +//@ #### exit(code) +//@ Exits the current process with the given exit code. +exports.exit = process.exit; + +//@ +//@ #### env['VAR_NAME'] +//@ Object containing environment variables (both getter and setter). Shortcut to process.env. +exports.env = process.env; + +//@ +//@ #### exec(command [, options] [, callback]) +//@ Available options (all `false` by default): +//@ +//@ + `async`: Asynchronous execution. Needs callback. +//@ + `silent`: Do not echo program output to console. +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var version = exec('node --version', {silent:true}).output; +//@ ``` +//@ +//@ Executes the given `command` _synchronously_, unless otherwise specified. +//@ 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'); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options = extend({ + silent: state.silent, + async: false + }, options); + + if (options.async) + execAsync(command, options, callback); + else + return execSync(command, options); +}; +exports.exec = wrap('exec', _exec, {notUnix:true}); + + + + +//@ +//@ ## Non-Unix commands +//@ + + + + + + +//@ +//@ #### tempdir() +//@ 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). +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'); + + if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + // if it's array leave it as it is + + var exists = true; + paths.forEach(function(p) { + if (!fs.existsSync(p)) + exists = false; + }); + + return exists; +}; +exports.exists = wrap('exists', _exists); + + +//@ +//@ #### 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; +} + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////// +// +// Auxiliary functions (internal use only) +// + +function log() { + if (!state.silent) + 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); +} + +// Shows error message. Throws unless '_continue = true'. +function error(msg, _continue) { + if (state.error === null) + state.error = ''; + state.error += state.currentCmd + ': ' + msg + '\n'; + + log(state.error); + + if (!_continue) + throw ''; +} + +// Returns {'alice': true, 'bob': false} when passed: +// parseOptions('-a', {'a':'alice', 'b':'bob'}); +function parseOptions(str, map) { + if (!map) + error('parseOptions() internal error: no map given'); + + // All options are false by default + var options = {}; + for (letter in map) + options[map[letter]] = false; + + if (!str) + return options; // defaults + + if (typeof str !== 'string') + error('parseOptions() internal error: wrong str'); + + // e.g. match[1] = 'Rf' for str = '-Rf' + var match = str.match(/^\-(.+)/); + if (!match) + return options; + + // e.g. chars = ['R', 'f'] + var chars = match[1].split(''); + + chars.forEach(function(char) { + if (char in map) + options[map[char]] = true; + else + error('option not recognized: '+char); + }); + + return options; +} + +// Common wrapper for all Unix-like commands +function wrap(cmd, fn, options) { + return function() { + var retValue = null; + + state.currentCmd = cmd; + state.error = null; + + try { + var args = [].slice.call(arguments, 0); + + if (options && options.notUnix) { + retValue = fn.apply(this, args); + } else { + if (args.length === 0 || typeof args[0] !== 'string' || args[0][0] !== '-') + args.unshift(''); // only add dummy option if '-option' not already present + retValue = fn.apply(this, args); + } + } 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('shell.js: internal error'); + console.log(e.stack || e); + process.exit(1); + } + if (state.fatal) + throw e; + } + + state.currentCmd = 'shell.js'; + return retValue; + } +} // wrap + +// Buffered file copy, synchronous +// (Using readFileSync() + writeFileSync() could easily cause a memory overflow +// with large files) +function copyFileSync(srcFile, destFile) { + if (!fs.existsSync(srcFile)) + error('copyFileSync: no such file or directory: ' + srcFile); + + var BUF_LENGTH = 64*1024, + buf = new Buffer(BUF_LENGTH), + bytesRead = BUF_LENGTH, + 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); + fs.writeSync(fdw, buf, 0, bytesRead); + pos += bytesRead; + } + + fs.closeSync(fdr); + fs.closeSync(fdw); +} + +// Recursively copies 'sourceDir' into 'destDir' +// Adapted from https://github.com/ryanmcgrath/wrench-js +// +// Copyright (c) 2010 Ryan McGrath +// Copyright (c) 2012 Artur Adib +// +// Licensed under the MIT License +// http://www.opensource.org/licenses/mit-license.php +function cpdirSyncRecursive(sourceDir, destDir, opts) { + if (!opts) opts = {}; + + /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */ + var checkDir = fs.statSync(sourceDir); + try { + fs.mkdirSync(destDir, checkDir.mode); + } catch (e) { + //if the directory already exists, that's okay + if (e.code !== 'EEXIST') throw e; + } + + var files = fs.readdirSync(sourceDir); + + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(sourceDir + "/" + files[i]); + + if (currFile.isDirectory()) { + /* recursion this thing right on back. */ + cpdirSyncRecursive(sourceDir + "/" + files[i], destDir + "/" + files[i], opts); + } else if (currFile.isSymbolicLink()) { + var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]); + fs.symlinkSync(symlinkFull, destDir + "/" + files[i]); + } else { + /* At this point, we've hit a file actually worth copying... so copy it on over. */ + if (fs.existsSync(destDir + "/" + files[i]) && !opts.force) { + log('skipping existing file: ' + files[i]); + } else { + copyFileSync(sourceDir + "/" + files[i], destDir + "/" + files[i]); + } + } + + } // for files +}; // cpdirSyncRecursive + +// Recursively removes 'dir' +// Adapted from https://github.com/ryanmcgrath/wrench-js +// +// Copyright (c) 2010 Ryan McGrath +// Copyright (c) 2012 Artur Adib +// +// Licensed under the MIT License +// http://www.opensource.org/licenses/mit-license.php +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 file = dir + "/" + files[i], + currFile = fs.lstatSync(file); + + if(currFile.isDirectory()) { // Recursive function back to the beginning + rmdirSyncRecursive(file, force); + } + + else if(currFile.isSymbolicLink()) { // Unlink symlinks + if (force || isWriteable(file)) + _unlinkSync(file); + } + + else // Assume it's a file - perhaps a try/catch belongs here? + 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. + + 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' +function mkdirSyncRecursive(dir) { + var baseDir = path.dirname(dir); + + // Base dir exists, no recursion necessary + if (fs.existsSync(baseDir)) { + fs.mkdirSync(dir, 0777); + return; + } + + // Base dir does not exist, go recursive + mkdirSyncRecursive(baseDir); + + // Base dir created, can create dir + fs.mkdirSync(dir, 0777); +}; + +// e.g. 'makerjs_a5f185d0443ca...' +function randomFileName() { + function randomHash(count) { + if (count === 1) + return parseInt(16*Math.random()).toString(16); + else { + var hash = ''; + for (var i=0; i&1'; // works on both win/unix + + var script = + "var child = require('child_process'), \ + fs = require('fs'); \ + child.exec('"+escape(cmd)+"', {env: process.env}, function(err) { \ + fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0'); \ + });"; + + 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, { + env: process.env, + cwd: exports.pwd() + }); + + // The wait loop + // 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. + var code = parseInt(''); + while (isNaN(code)) + code = parseInt(fs.readFileSync(codeFile, 'utf8')); + + var stdout = fs.readFileSync(stdoutFile, 'utf8'); + + // 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, + output: stdout + }; + return obj; +} // execSync() + +// Expands wildcards with matching file names. For a given array of file names 'list', returns +// another array containing all file names as per ls(list[i]). +// For example: expand(['file*.js']) = ['file1.js', 'file2.js', ...] +// (if the files 'file1.js', 'file2.js', etc, exist in the current dir) +function expand(list) { + var expanded = []; + list.forEach(function(listEl) { + // Wildcard present? + if (listEl.search(/\*/) > -1) { + _ls('', listEl).forEach(function(file) { + expanded.push(file); + }); + } else { + expanded.push(listEl); + } + }); + return expanded; +} + +// Cross-platform method for splitting environment PATH variables +function splitPath(p) { + if (!p) + return []; + + if (platform === 'win') + return p.split(';'); + else + return p.split(':'); +} + +// extend(target_obj, source_obj1 [, source_obj2 ...]) +// Shallow extend, e.g.: +// aux.extend({a:1}, {b:2}, {c:3}) +// returns {a:1, b:2, c:3} +function extend(target) { + var sources = [].slice.call(arguments, 1); + sources.forEach(function(source) { + for (key in source) + target[key] = source[key]; + }); + + 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 new file mode 100755 index 000000000..e0975fec8 --- /dev/null +++ b/make.js @@ -0,0 +1,482 @@ +#!/usr/bin/env node +require('./external/shelljs/make'); + +var ROOT_DIR = __dirname + '/', // absolute path to project's root + BUILD_DIR = 'build/', + BUILD_TARGET = BUILD_DIR + 'pdf.js', + FIREFOX_BUILD_DIR = BUILD_DIR + '/firefox/', + EXTENSION_SRC_DIR = 'extensions/', + GH_PAGES_DIR = BUILD_DIR + 'gh-pages/', + REPO = 'git@github.com:mozilla/pdf.js.git', + PYTHON_BIN = 'python2.7'; + +// +// make all +// +target.all = function() { + // Don't do anything by default + echo('Please specify a target. Available targets:'); + for (t in target) + if (t !== 'all') echo(' ' + t); +}; + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Production stuff +// + +// +// make web +// Generates the website for the project, by checking out the gh-pages branch underneath +// the build directory, and then moving the various viewer files into place. +// +target.web = function() { + target.production(); + target.extension(); + target.pagesrepo(); + + cd(ROOT_DIR); + echo(); + echo('### Creating web site'); + + cp(BUILD_TARGET, GH_PAGES_DIR + BUILD_TARGET); + cp('-R', 'web/*', GH_PAGES_DIR + '/web'); + cp(FIREFOX_BUILD_DIR + '/*.xpi', FIREFOX_BUILD_DIR + '/*.rdf', + GH_PAGES_DIR + EXTENSION_SRC_DIR + 'firefox/'); + cp(GH_PAGES_DIR + '/web/index.html.template', GH_PAGES_DIR + '/index.html'); + mv('-f', GH_PAGES_DIR + '/web/viewer-production.html', + GH_PAGES_DIR + '/web/viewer.html'); + cd(GH_PAGES_DIR); + exec('git add -A'); + + echo(); + echo("Website built in " + GH_PAGES_DIR); + echo("Don't forget to cd into " + GH_PAGES_DIR + + " and issue 'git commit' to push changes."); +}; + +// +// make production +// Creates production output (pdf.js, and corresponding changes to web/ files) +// +target.production = function() { + target.bundle(); + target.viewer(); +}; + +// +// make bundle +// Bundles all source files into one wrapper 'pdf.js' file, in the given order. +// + +target.bundle = function() { + cd(ROOT_DIR); + echo(); + echo('### Bundling files into ' + BUILD_TARGET); + + // File order matters + var SRC_FILES = + ['core.js', + 'util.js', + 'canvas.js', + 'obj.js', + 'function.js', + 'charsets.js', + 'cidmaps.js', + 'colorspace.js', + 'crypto.js', + 'evaluator.js', + 'fonts.js', + 'glyphlist.js', + 'image.js', + 'metrics.js', + 'parser.js', + 'pattern.js', + 'stream.js', + 'worker.js', + '../external/jpgjs/jpg.js', + 'jpx.js', + 'bidi.js', + 'metadata.js']; + + if (!test('-d', BUILD_DIR)) + mkdir(BUILD_DIR); + + cd('src'); + var bundle = cat(SRC_FILES), + bundleVersion = exec('git log --format="%h" -n 1', + {silent: true}).output.replace('\n', ''); + + sed(/.*PDFJSSCRIPT_INCLUDE_ALL.*\n/, bundle, 'pdf.js') + .to(ROOT_DIR + BUILD_TARGET); + sed('-i', 'PDFJSSCRIPT_BUNDLE_VER', bundleVersion, ROOT_DIR + BUILD_TARGET); +}; + +// +// make viewer +// Changes development '; + // Hack so the end script tag isn't counted if this is inline JS. + src += ''; for (var i = 0, ii = names.length; i < ii; ++i) { src += '

Hi

'; } @@ -736,6 +737,16 @@ function getUnicodeRangeFor(value) { return -1; } +function isRTLRangeFor(value) { + var range = UnicodeRanges[13]; + if (value >= range.begin && value < range.end) + return true; + range = UnicodeRanges[11]; + if (value >= range.begin && value < range.end) + return true; + return false; +} + function isSpecialUnicode(unicode) { return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) || (unicode >= kCmapGlyphOffset && @@ -1484,15 +1495,15 @@ var Font = (function FontClosure() { function Font(name, file, properties) { this.name = name; this.coded = properties.coded; - this.charProcIRQueues = properties.charProcIRQueues; + this.charProcOperatorList = properties.charProcOperatorList; this.resources = properties.resources; this.sizes = []; var names = name.split('+'); names = names.length > 1 ? names[1] : names[0]; names = names.split(/[-,_]/g)[0]; - this.isSerifFont = !!(properties.flags & 2); - this.isSymbolicFont = !!(properties.flags & 4); + this.isSerifFont = !!(properties.flags & FontFlags.Serif); + this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); var type = properties.type; this.type = type; @@ -1526,6 +1537,8 @@ var Font = (function FontClosure() { else this.rebuildToUnicode(properties); + this.toFontChar = this.buildToFontChar(this.toUnicode); + if (!file) { // The file data is not specified. Trying to fix the font name // to be used with the canvas.font. @@ -1555,7 +1568,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); @@ -1580,7 +1593,7 @@ var Font = (function FontClosure() { this.widthMultiplier = !properties.fontMatrix ? 1.0 : 1.0 / properties.fontMatrix[0]; this.encoding = properties.baseEncoding; - this.loadedName = getUniqueName(); + this.loadedName = properties.loadedName; this.loading = true; }; @@ -1984,7 +1997,7 @@ var Font = (function FontClosure() { mimetype: null, encoding: null, - checkAndRepair: function font_checkAndRepair(name, font, properties) { + checkAndRepair: function Font_checkAndRepair(name, font, properties) { function readTableEntry(file) { var tag = file.getBytes(4); tag = String.fromCharCode(tag[0]) + @@ -2256,6 +2269,61 @@ var Font = (function FontClosure() { } }; + function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) { + if (sourceEnd - sourceStart <= 12) { + // glyph with data less than 12 is invalid one + return 0; + } + var glyf = source.subarray(sourceStart, sourceEnd); + var contoursCount = (glyf[0] << 8) | glyf[1]; + if (contoursCount & 0x8000) { + // complex glyph, writing as is + dest.set(glyf, destStart); + return glyf.length; + } + + var j = 10, flagsCount = 0; + for (var i = 0; i < contoursCount; i++) { + var endPoint = (glyf[j] << 8) | glyf[j + 1]; + flagsCount = endPoint + 1; + j += 2; + } + // skipping instructions + var instructionsLength = (glyf[j] << 8) | glyf[j + 1]; + j += 2 + instructionsLength; + // validating flags + var coordinatesLength = 0; + for (var i = 0; i < flagsCount; i++) { + var flag = glyf[j++]; + if (flag & 0xC0) { + // reserved flags must be zero, rejecting + return 0; + } + var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) + + ((flag & 4) ? 1 : (flag & 32) ? 0 : 2); + coordinatesLength += xyLength; + if (flag & 8) { + var repeat = glyf[j++]; + i += repeat; + coordinatesLength += repeat * xyLength; + } + } + var glyphDataLength = j + coordinatesLength; + if (glyphDataLength > glyf.length) { + // not enough data for coordinates + return 0; + } + if (glyf.length - glyphDataLength > 3) { + // truncating and aligning to 4 bytes the long glyph data + glyphDataLength = (glyphDataLength + 3) & ~3; + dest.set(glyf.subarray(0, glyphDataLength), destStart); + return glyphDataLength; + } + // glyph data is fine + dest.set(glyf, destStart); + return glyf.length; + } + function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong) { var itemSize, itemDecode, itemEncode; @@ -2282,20 +2350,64 @@ var Font = (function FontClosure() { }; } var locaData = loca.data; + // removing the invalid glyphs + var oldGlyfData = glyf.data; + var oldGlyfDataLength = oldGlyfData.length; + var newGlyfData = new Uint8Array(oldGlyfDataLength); var startOffset = itemDecode(locaData, 0); - var firstOffset = itemDecode(locaData, itemSize); - if (firstOffset - startOffset < 12 || startOffset > 0) { - // removing first glyph - glyf.data = glyf.data.subarray(firstOffset); - glyf.length -= firstOffset; - - itemEncode(locaData, 0, 0); - var i, pos = itemSize; - for (i = 1; i <= numGlyphs; ++i) { - itemEncode(locaData, pos, - itemDecode(locaData, pos) - firstOffset); - pos += itemSize; + var writeOffset = 0; + itemEncode(locaData, 0, writeOffset); + for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { + var endOffset = itemDecode(locaData, j); + if (endOffset > oldGlyfDataLength) { + // glyph end offset points outside glyf data, rejecting the glyph + itemEncode(locaData, j, writeOffset); + startOffset = endOffset; + continue; } + + var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset, + newGlyfData, writeOffset); + writeOffset += newLength; + itemEncode(locaData, j, writeOffset); + startOffset = endOffset; + } + + if (writeOffset == 0) { + // glyf table cannot be empty -- redoing the glyf and loca tables + // to have single glyph with one point + var simpleGlyph = new Uint8Array( + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]); + for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) + itemEncode(locaData, j, simpleGlyph.length); + glyf.data = simpleGlyph; + return; + } + + 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; } } @@ -2425,11 +2537,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) { @@ -2442,8 +2558,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 @@ -2475,14 +2592,16 @@ var Font = (function FontClosure() { } } - var glyphs = [], ids = []; + glyphs = []; + ids = []; + var usedUnicodes = []; var unassignedUnicodeItems = []; for (var i = 1; i < numGlyphs; i++) { var cid = gidToCidMap[i] || i; - var unicode = this.toUnicode[cid]; - if (!unicode || isSpecialUnicode(unicode) || - unicode in usedUnicodes) { + var unicode = this.toFontChar[cid]; + if (!unicode || typeof unicode !== 'number' || + isSpecialUnicode(unicode) || unicode in usedUnicodes) { unassignedUnicodeItems.push(i); continue; } @@ -2501,20 +2620,32 @@ var Font = (function FontClosure() { if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea) break; var unicode = unusedUnicode++; - this.toUnicode[cid] = unicode; + this.toFontChar[cid] = unicode; usedUnicodes[unicode] = true; 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; - var hasShortCmap = !!cmapTable.hasShortCmap; - var toUnicode = this.toUnicode; - if (toUnicode && toUnicode.length > 0) { + glyphs = cmapTable.glyphs; + ids = cmapTable.ids; + + var hasShortCmap = !!cmapTable.hasShortCmap; + var toFontChar = this.toFontChar; + + if (hasShortCmap && ids.length == numGlyphs) { + // Fixes the short cmap tables -- some generators use incorrect + // glyph id. + for (var i = 0, ii = ids.length; i < ii; i++) + ids[i] = i; + } + + var unusedUnicode = kCmapGlyphOffset; + var glyphNames = properties.glyphNames || []; + var encoding = properties.baseEncoding; + var differences = properties.differences; + if (toFontChar && toFontChar.length > 0) { // checking if cmap is just identity map var isIdentity = true; for (var i = 0, ii = glyphs.length; i < ii; i++) { @@ -2524,32 +2655,64 @@ var Font = (function FontClosure() { } } // if it is, replacing with meaningful toUnicode values - if (isIdentity) { + if (isIdentity && !this.isSymbolicFont) { var usedUnicodes = [], unassignedUnicodeItems = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { - var unicode = toUnicode[i + 1]; - if (!unicode || unicode in usedUnicodes) { + var unicode = toFontChar[i + 1]; + if (!unicode || typeof unicode !== 'number' || + unicode in usedUnicodes) { unassignedUnicodeItems.push(i); continue; } glyphs[i].unicode = unicode; usedUnicodes[unicode] = true; } - var unusedUnicode = kCmapGlyphOffset; for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) { var i = unassignedUnicodeItems[j]; while (unusedUnicode in usedUnicodes) unusedUnicode++; var cid = i + 1; // override only if unicode mapping is not specified - if (!(cid in toUnicode)) - toUnicode[cid] = unusedUnicode; + if (!(cid in toFontChar)) + toFontChar[cid] = unusedUnicode; glyphs[i].unicode = unusedUnicode++; } - this.useToUnicode = true; + this.useToFontChar = true; } } + // 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: @@ -2558,9 +2721,11 @@ var Font = (function FontClosure() { var usedUnicodes = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; + var gid = ids[i]; glyphs[i].unicode += kCmapGlyphOffset; + toFontChar[code] = glyphs[i].unicode; - var glyphName = properties.baseEncoding[code]; + var glyphName = glyphNames[gid] || encoding[code]; if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; if (unicode in usedUnicodes) @@ -2571,23 +2736,76 @@ var Font = (function FontClosure() { unicode: unicode, code: glyphs[i].code }); - ids.push(ids[i]); + ids.push(gid); + toFontChar[code] = unicode; } } + this.useToFontChar = true; + } else if (!this.isSymbolicFont && (this.hasEncoding || + properties.glyphNames || differences.length > 0)) { + // Re-encode cmap encoding to unicode, based on the 'post' table data + // diffrence array or base encoding + var reverseMap = []; + 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; + var gid = ids[i]; + + var glyphName = glyphNames[gid]; + if (!glyphName) { + glyphName = differences[code] || encoding[code]; + changeCode = true; + } + if (glyphName in GlyphsUnicode) { + var unicode = GlyphsUnicode[glyphName]; + if (!unicode || reverseMap[unicode] === i) + continue; // unknown glyph name or in its own place + + newGlyphUnicodes[i] = unicode; + if (changeCode) + toFontChar[code] = unicode; + delete reverseMap[code]; + } + } + 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; } - // 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; + // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. + if (this.isSymbolicFont) { + for (var i = 0, ii = glyphs.length; i < ii; i++) { + var code = glyphs[i].unicode & 0xFF; + var fontCharCode = kSymbolicFontGlyphOffset | code; + glyphs[i].unicode = toFontChar[code] = fontCharCode; + } + this.useToFontChar = true; } 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({ @@ -2634,7 +2852,7 @@ var Font = (function FontClosure() { return stringToArray(ttf.file); }, - convert: function font_convert(fontName, font, properties) { + convert: function Font_convert(fontName, font, properties) { function isFixedPitch(glyphs) { for (var i = 0, ii = glyphs.length - 1; i < ii; i++) { if (glyphs[i] != glyphs[i + 1]) @@ -2676,12 +2894,12 @@ var Font = (function FontClosure() { properties.baseEncoding = encoding; } if (properties.subtype == 'CIDFontType0C') { - var toUnicode = []; + var toFontChar = []; for (var i = 0; i < charstrings.length; ++i) { var charstring = charstrings[i]; - toUnicode[charstring.code] = charstring.unicode; + toFontChar[charstring.code] = charstring.unicode; } - this.toUnicode = toUnicode; + this.toFontChar = toFontChar; } var fields = { @@ -2776,7 +2994,20 @@ var Font = (function FontClosure() { return stringToArray(otf.file); }, - rebuildToUnicode: function font_rebuildToUnicode(properties) { + buildToFontChar: function Font_buildToFontChar(toUnicode) { + var result = []; + var unusedUnicode = kCmapGlyphOffset; + for (var i = 0, ii = toUnicode.length; i < ii; i++) { + var unicode = toUnicode[i]; + var fontCharCode = typeof unicode === 'object' ? unusedUnicode++ : + unicode; + if (typeof unicode !== 'undefined') + result[i] = fontCharCode; + } + return result; + }, + + rebuildToUnicode: function Font_rebuildToUnicode(properties) { var firstChar = properties.firstChar, lastChar = properties.lastChar; var map = []; if (properties.composite) { @@ -2798,7 +3029,7 @@ var Font = (function FontClosure() { this.toUnicode = map; }, - loadCidToUnicode: function font_loadCidToUnicode(properties) { + loadCidToUnicode: function Font_loadCidToUnicode(properties) { if (!properties.cidSystemInfo) return; @@ -2847,18 +3078,7 @@ var Font = (function FontClosure() { } }, - bindWorker: function font_bindWorker(data) { - postMessage({ - action: 'font', - data: { - raw: data, - fontName: this.loadedName, - mimetype: this.mimetype - } - }); - }, - - bindDOM: function font_bindDom(data) { + bindDOM: function Font_bindDOM(data) { var fontName = this.loadedName; // Add the font-face rule to the document @@ -2873,6 +3093,9 @@ var Font = (function FontClosure() { var styleSheet = styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); + if (PDFJS.pdfBug && FontInspector.enabled) + FontInspector.fontAdded(this, url); + return rule; }, @@ -2907,8 +3130,8 @@ var Font = (function FontClosure() { return shadow(this, 'spaceWidth', width); }, - charToGlyph: function fonts_charToGlyph(charcode) { - var fontChar, width, codeIRQueue; + charToGlyph: function Font_charToGlyph(charcode) { + var fontCharCode, width, operatorList, disabled; var width = this.widths[charcode]; @@ -2916,38 +3139,39 @@ var Font = (function FontClosure() { case 'CIDFontType0': if (this.noUnicodeAdaptation) { width = this.widths[this.unicodeToCID[charcode] || charcode]; - fontChar = charcode; + fontCharCode = mapPrivateUseChars(charcode); break; } - fontChar = this.toUnicode[charcode] || charcode; + fontCharCode = this.toFontChar[charcode] || charcode; break; case 'CIDFontType2': if (this.noUnicodeAdaptation) { width = this.widths[this.unicodeToCID[charcode] || charcode]; - fontChar = charcode; + fontCharCode = mapPrivateUseChars(charcode); break; } - fontChar = this.toUnicode[charcode] || charcode; + fontCharCode = this.toFontChar[charcode] || charcode; break; case 'Type1': var glyphName = this.differences[charcode] || this.encoding[charcode]; if (!isNum(width)) width = this.widths[glyphName]; if (this.noUnicodeAdaptation) { - fontChar = GlyphsUnicode[glyphName] || charcode; + fontCharCode = mapPrivateUseChars(GlyphsUnicode[glyphName] || + charcode); break; } - fontChar = this.glyphNameMap[glyphName] || + fontCharCode = this.glyphNameMap[glyphName] || GlyphsUnicode[glyphName] || charcode; break; case 'Type3': var glyphName = this.differences[charcode] || this.encoding[charcode]; - codeIRQueue = this.charProcIRQueues[glyphName]; - fontChar = charcode; + operatorList = this.charProcOperatorList[glyphName]; + fontCharCode = charcode; break; case 'TrueType': - if (this.useToUnicode) { - fontChar = this.toUnicode[charcode] || charcode; + if (this.useToFontChar) { + fontCharCode = this.toFontChar[charcode] || charcode; break; } var glyphName = this.differences[charcode] || this.encoding[charcode]; @@ -2956,41 +3180,44 @@ var Font = (function FontClosure() { if (!isNum(width)) width = this.widths[glyphName]; if (this.noUnicodeAdaptation) { - fontChar = GlyphsUnicode[glyphName] || charcode; + fontCharCode = GlyphsUnicode[glyphName] || charcode; break; } if (!this.hasEncoding || this.isSymbolicFont) { - fontChar = this.useToUnicode ? this.toUnicode[charcode] : charcode; + fontCharCode = this.useToFontChar ? this.toFontChar[charcode] : + charcode; break; } // MacRoman encoding address by re-encoding the cmap table - fontChar = glyphName in GlyphsUnicode ? - GlyphsUnicode[glyphName] : - this.glyphNameMap[glyphName]; + + fontCharCode = glyphName in this.glyphNameMap ? + this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName]; break; default: warn('Unsupported font type: ' + this.type); break; } - var unicodeChars = !('toUnicode' in this) ? fontChar : - this.toUnicode[charcode] || fontChar; + var unicodeChars = !('toUnicode' in this) ? charcode : + this.toUnicode[charcode] || charcode; if (typeof unicodeChars === 'number') unicodeChars = String.fromCharCode(unicodeChars); width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier; + disabled = this.unicodeIsEnabled ? + !this.unicodeIsEnabled[fontCharCode] : false; return { - fontChar: String.fromCharCode(fontChar), - unicode: (unicodeChars in NormalizedUnicodes) ? - NormalizedUnicodes[unicodeChars] : unicodeChars, + fontChar: String.fromCharCode(fontCharCode), + unicode: unicodeChars, width: width, - codeIRQueue: codeIRQueue + disabled: disabled, + operatorList: operatorList }; }, - charsToGlyphs: function fonts_charsToGlyphs(chars) { + charsToGlyphs: function Font_charsToGlyphs(chars) { var charsCache = this.charsCache; var glyphs; @@ -3325,7 +3552,13 @@ var Type1Parser = function type1Parser() { while (str[index++] != ']') count++; - var array = str.substr(start, count).split(' '); + str = str.substr(start, count); + + str = str.trim(); + // Remove adjacent spaces + str = str.replace(/\s+/g, ' '); + + var array = str.split(' '); for (var i = 0, ii = array.length; i < ii; i++) array[i] = parseFloat(array[i] || 0); return array; @@ -3348,7 +3581,7 @@ var Type1Parser = function type1Parser() { return c == ' ' || c == '\n' || c == '\x0d'; } - this.extractFontProgram = function t1_extractFontProgram(stream) { + this.extractFontProgram = function Type1Parser_extractFontProgram(stream) { var eexec = decrypt(stream, kEexecEncryptionKey, 4); var eexecStr = ''; for (var i = 0, ii = eexec.length; i < ii; i++) @@ -3359,7 +3592,7 @@ var Type1Parser = function type1Parser() { subrs: [], charstrings: [], properties: { - 'private': { + 'privateData': { 'lenIV': 4 } } @@ -3388,7 +3621,7 @@ var Type1Parser = function type1Parser() { (token == 'RD' || token == '-|')) { i++; var data = eexec.slice(i, i + length); - var lenIV = program.properties.private['lenIV']; + var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, kCharStringsEncryptionKey, lenIV); var str = decodeCharString(encoded); @@ -3428,7 +3661,7 @@ var Type1Parser = function type1Parser() { var length = parseInt(getToken(), 10); getToken(); // read in 'RD' var data = eexec.slice(i + 1, i + 1 + length); - var lenIV = program.properties.private['lenIV']; + var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, kCharStringsEncryptionKey, lenIV); var str = decodeCharString(encoded); i = i + 1 + length; @@ -3444,12 +3677,12 @@ var Type1Parser = function type1Parser() { case '/FamilyOtherBlues': case '/StemSnapH': case '/StemSnapV': - program.properties.private[token.substring(1)] = + program.properties.privateData[token.substring(1)] = readNumberArray(eexecStr, i + 1); break; case '/StdHW': case '/StdVW': - program.properties.private[token.substring(1)] = + program.properties.privateData[token.substring(1)] = readNumberArray(eexecStr, i + 2)[0]; break; case '/BlueShift': @@ -3458,7 +3691,7 @@ var Type1Parser = function type1Parser() { case '/BlueScale': case '/LanguageGroup': case '/ExpansionFactor': - program.properties.private[token.substring(1)] = + program.properties.privateData[token.substring(1)] = readNumber(eexecStr, i + 1); break; } @@ -3473,7 +3706,8 @@ var Type1Parser = function type1Parser() { return program; }; - this.extractFontHeader = function t1_extractFontHeader(stream, properties) { + this.extractFontHeader = function Type1Parser_extractFontHeader(stream, + properties) { var headerString = ''; for (var i = 0, ii = stream.length; i < ii; i++) headerString += String.fromCharCode(stream[i]); @@ -3482,14 +3716,14 @@ var Type1Parser = function type1Parser() { var count = headerString.length; for (var i = 0; i < count; i++) { var getToken = function getToken() { - var char = headerString[i]; - while (i < count && (isSeparator(char) || char == '/')) - char = headerString[++i]; + var character = headerString[i]; + while (i < count && (isSeparator(character) || character == '/')) + character = headerString[++i]; var token = ''; - while (i < count && !(isSeparator(char) || char == '/')) { - token += char; - char = headerString[++i]; + while (i < count && !(isSeparator(character) || character == '/')) { + token += character; + character = headerString[++i]; } return token; @@ -3549,7 +3783,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', @@ -3619,7 +3853,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); @@ -3639,8 +3874,9 @@ var CFF = function cffCFF(name, file, properties) { subrs, properties); }; -CFF.prototype = { - createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) { +Type1Font.prototype = { + createCFFIndexHeader: function Type1Font_createCFFIndexHeader(objects, + isByte) { // First 2 bytes contains the number of objects contained into this index var count = objects.length; @@ -3676,7 +3912,7 @@ CFF.prototype = { return data; }, - encodeNumber: function cff_encodeNumber(value) { + encodeNumber: function Type1Font_encodeNumber(value) { // some of the fonts has ouf-of-range values // they are just arithmetic overflows // make sanitizer happy @@ -3694,7 +3930,7 @@ CFF.prototype = { } }, - getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs, + getOrderedCharStrings: function Type1Font_getOrderedCharStrings(glyphs, properties) { var charstrings = []; var i, length, glyphName; @@ -3720,7 +3956,8 @@ CFF.prototype = { return charstrings; }, - getType2Charstrings: function cff_getType2Charstrings(type1Charstrings) { + getType2Charstrings: function Type1Font_getType2Charstrings( + type1Charstrings) { var type2Charstrings = []; var count = type1Charstrings.length; for (var i = 0; i < count; i++) { @@ -3731,7 +3968,7 @@ CFF.prototype = { return type2Charstrings; }, - getType2Subrs: function cff_getType2Subrs(type1Subrs) { + getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { var bias = 0; var count = type1Subrs.length; if (count < 1240) @@ -3783,7 +4020,7 @@ CFF.prototype = { 'hvcurveto': 31 }, - flattenCharstring: function flattenCharstring(charstring, map) { + flattenCharstring: function Type1Font_flattenCharstring(charstring, map) { // charstring changes size - can't cache .length in loop for (var i = 0; i < charstring.length; i++) { var command = charstring[i]; @@ -3810,7 +4047,7 @@ CFF.prototype = { return charstring; }, - wrap: function wrap(name, glyphs, charstrings, subrs, properties) { + wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) { var fields = { // major version, minor version, header size, offset size 'header': '\x01\x00\x04\x04', @@ -3854,7 +4091,7 @@ CFF.prototype = { dict += self.encodeNumber(offset) + '\x11'; // Charstrings offset = offset + fields.charstrings.length; - dict += self.encodeNumber(fields.private.length); + dict += self.encodeNumber(fields.privateData.length); dict += self.encodeNumber(offset) + '\x12'; // Private return header + String.fromCharCode(dict.length + 1) + dict; @@ -3879,7 +4116,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 @@ -3895,7 +4132,7 @@ CFF.prototype = { 'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true), - 'private': (function cffWrapPrivate(self) { + 'privateData': (function cffWrapPrivate(self) { var data = '\x8b\x14' + // defaultWidth '\x8b\x15'; // nominalWidth @@ -3913,9 +4150,9 @@ CFF.prototype = { ExpansionFactor: '\x0c\x18' }; for (var field in fieldMap) { - if (!properties.private.hasOwnProperty(field)) + if (!properties.privateData.hasOwnProperty(field)) continue; - var value = properties.private[field]; + var value = properties.privateData[field]; if (isArray(value)) { data += self.encodeNumber(value[0]); @@ -3948,104 +4185,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); - - 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 CFFFont_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 = []; @@ -4054,21 +4218,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 CFFFont_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') @@ -4104,251 +4265,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 CFFParser_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'); - } - } - return dict; - }, - 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 CFFParser_parseHeader() { var bytes = this.bytes; var offset = 0; @@ -4356,17 +4346,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 CFFParser_parseDict(dict) { var pos = 0; function parseOperand() { @@ -4383,11 +4374,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'); @@ -4425,27 +4416,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; @@ -4455,10 +4427,12 @@ var Type2CFF = (function Type2CFFClosure() { } return entries; }, - parseIndex: function cff_parseIndex(pos) { + parseIndex: function CFFParser_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) { @@ -4476,26 +4450,947 @@ 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 CFFParser_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 CFFParser_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 CFFParser_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 CFFParser_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 CFFParser_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 CFFParser_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 CFFParser_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 CFFParser_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 CFFStrings_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 CFFStrings_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 CFFIndex_add(data) { + this.length += data.length; + this.objects.push(data); + }, + get: function CFFIndex_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 CFFDict_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 CFFDict_hasName(name) { + return this.nameToKeyMap[name] in this.values; + }, + getByName: function CFFDict_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 CFFDict_removeByName(name) { + delete this.values[this.nameToKeyMap[name]]; + } + }; + CFFDict.createTables = function CFFDict_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 CFFOffsetTracker_isTracking(key) { + return key in this.offsets; + }, + track: function CFFOffsetTracker_track(key, location) { + if (key in this.offsets) + error('Already tracking location of ' + key); + this.offsets[key] = location; + }, + offset: function CFFOffsetTracker_offset(value) { + for (var key in this.offsets) { + this.offsets[key] += value; + } + }, + setEntryLocation: function CFFOffsetTracker_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 CFFCompiler_compile() { + var cff = this.cff; + var output = { + data: [], + length: 0, + add: function CFFCompiler_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) { + // For some reason FDSelect must be in front of FDArray on windows. OSX + // and linux don't seem to care. + topDictTracker.setEntryLocation('FDSelect', [output.length], output); + var fdSelect = this.compileFDSelect(cff.fdSelect.raw); + output.add(fdSelect); + + 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); + } + + this.compilePrivateDicts([cff.topDict], [topDictTracker], output); + + return output.data; + }, + encodeNumber: function CFFCompiler_encodeNumber(value) { + if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt + return this.encodeInteger(value); + else + return this.encodeFloat(value); + }, + encodeFloat: function CFFCompiler_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 CFFCompiler_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 CFFCompiler_compileHeader(header) { + return [ + header.major, + header.minor, + header.hdrSize, + header.offSize + ]; + }, + compileNameIndex: function CFFCompiler_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 CFFCompiler_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 CFFCompiler_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 CFFCompiler_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 CFFCompiler_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 CFFCompiler_compileGlobalSubrIndex() { + var globalSubrIndex = this.cff.globalSubrIndex; + this.out.writeByteArray(this.compileIndex(globalSubrIndex)); + }, + compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) { + return this.compileIndex(charStrings); + }, + compileCharset: function CFFCompiler_compileCharset(charset) { + return this.compileTypedArray(charset.raw); + }, + compileEncoding: function CFFCompiler_compileEncoding(encoding) { + return this.compileTypedArray(encoding.raw); + }, + compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) { + return this.compileTypedArray(fdSelect); + }, + compileTypedArray: function CFFCompiler_compileTypedArray(data) { + var out = []; + for (var i = 0, ii = data.length; i < ii; ++i) + out[i] = data[i]; + return out; + }, + compileIndex: function CFFCompiler_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 26b8fe679..2e7ad45e6 100644 --- a/src/function.js +++ b/src/function.js @@ -10,7 +10,7 @@ var PDFFunction = (function PDFFunctionClosure() { var CONSTRUCT_POSTSCRIPT = 4; return { - getSampleArray: function pdfFunctionGetSampleArray(size, outputSize, bps, + getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) { var length = 1; for (var i = 0, ii = size.length; i < ii; i++) @@ -38,7 +38,7 @@ var PDFFunction = (function PDFFunctionClosure() { return array; }, - getIR: function pdfFunctionGetIR(xref, fn) { + getIR: function PDFFunction_getIR(xref, fn) { var dict = fn.dict; if (!dict) dict = fn; @@ -57,7 +57,7 @@ var PDFFunction = (function PDFFunctionClosure() { return typeFn.call(this, fn, dict, xref); }, - fromIR: function pdfFunctionFromIR(IR) { + fromIR: function PDFFunction_fromIR(IR) { var type = IR[0]; switch (type) { case CONSTRUCT_SAMPLED: @@ -72,16 +72,16 @@ var PDFFunction = (function PDFFunctionClosure() { } }, - parse: function pdfFunctionParse(xref, fn) { + parse: function PDFFunction_parse(xref, fn) { var IR = this.getIR(xref, fn); return this.fromIR(IR); }, - constructSampled: function pdfFunctionConstructSampled(str, dict) { + constructSampled: function PDFFunction_constructSampled(str, dict) { 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]]; @@ -125,114 +125,104 @@ var PDFFunction = (function PDFFunctionClosure() { else decode = toMultiArray(decode); - // Precalc the multipliers - var inputMul = new Float64Array(inputSize); - for (var i = 0; i < inputSize; ++i) { - inputMul[i] = (encode[i][1] - encode[i][0]) / - (domain[i][1] - domain[i][0]); - } - - var idxMul = new Int32Array(inputSize); - idxMul[0] = outputSize; - for (i = 1; i < inputSize; ++i) { - idxMul[i] = idxMul[i - 1] * size[i - 1]; - } - - var nSamples = outputSize; - for (i = 0; i < inputSize; ++i) - nSamples *= size[i]; - var samples = this.getSampleArray(size, outputSize, bps, str); return [ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, bps, range, inputMul, idxMul, nSamples + outputSize, Math.pow(2, bps) - 1, range ]; }, - constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) { - var inputSize = IR[1]; - var domain = IR[2]; - var encode = IR[3]; - var decode = IR[4]; - var samples = IR[5]; - var size = IR[6]; - var outputSize = IR[7]; - var bps = IR[8]; - var range = IR[9]; - var inputMul = IR[10]; - var idxMul = IR[11]; - var nSamples = IR[12]; + constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { + // See chapter 3, page 109 of the PDF reference + function interpolate(x, xmin, xmax, ymin, ymax) { + return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); + } return function constructSampledFromIRResult(args) { - if (inputSize != args.length) + // See chapter 3, page 110 of the PDF reference. + var m = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4]; + var samples = IR[5]; + var size = IR[6]; + var n = IR[7]; + var mask = IR[8]; + var range = IR[9]; + + if (m != args.length) error('Incorrect number of arguments: ' + inputSize + ' != ' + args.length); - // Most of the below is a port of Poppler's implementation. - // TODO: There's a few other ways to do multilinear interpolation such - // as piecewise, which is much faster but an approximation. - var out = new Float64Array(outputSize); - var x; - var e = new Array(inputSize); - var efrac0 = new Float64Array(inputSize); - var efrac1 = new Float64Array(inputSize); - var sBuf = new Float64Array(1 << inputSize); - var i, j, k, idx, t; - // map input values into sample array - for (i = 0; i < inputSize; ++i) { - x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0]; - if (x < 0) { - x = 0; - } else if (x > size[i] - 1) { - x = size[i] - 1; - } - e[i] = [Math.floor(x), 0]; - if ((e[i][1] = e[i][0] + 1) >= size[i]) { - // this happens if in[i] = domain[i][1] - e[i][1] = e[i][0]; - } - efrac1[i] = x - e[i][0]; - efrac0[i] = 1 - efrac1[i]; - } + var x = args; - // for each output, do m-linear interpolation - for (i = 0; i < outputSize; ++i) { + // Building the cube vertices: its part and sample index + // http://rjwagner49.com/Mathematics/Interpolation.pdf + var cubeVertices = 1 << m; + var cubeN = new Float64Array(cubeVertices); + var cubeVertex = new Uint32Array(cubeVertices); + for (var j = 0; j < cubeVertices; j++) + cubeN[j] = 1; - // pull 2^m values out of the sample array - for (j = 0; j < (1 << inputSize); ++j) { - idx = i; - for (k = 0, t = j; k < inputSize; ++k, t >>= 1) { - idx += idxMul[k] * (e[k][t & 1]); - } - if (idx >= 0 && idx < nSamples) { - sBuf[j] = samples[idx]; + var k = n, pos = 1; + // Map x_i to y_j for 0 <= i < m using the sampled function. + for (var i = 0; i < m; ++i) { + // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) + var domain_2i = domain[i][0]; + var domain_2i_1 = domain[i][1]; + var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, + // Encode_2i, Encode_2i+1) + var e = interpolate(xi, domain_2i, domain_2i_1, + encode[i][0], encode[i][1]); + + // e_i' = min(max(e_i, 0), Size_i - 1) + var size_i = size[i]; + e = Math.min(Math.max(e, 0), size_i - 1); + + // Adjusting the cube: N and vertex sample index + var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; + var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); + var n1 = e - e0; // (e - e0) / (e1 - e0); + var offset0 = e0 * k; + var offset1 = offset0 + k; // e1 * k + for (var j = 0; j < cubeVertices; j++) { + if (j & pos) { + cubeN[j] *= n1; + cubeVertex[j] += offset1; } else { - sBuf[j] = 0; // TODO Investigate if this is what Adobe does + cubeN[j] *= n0; + cubeVertex[j] += offset0; } } - // do m sets of interpolations - for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) { - for (k = 0; k < t; k += 2) { - sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1]; - } - } - - // map output value to range - out[i] = (sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]); - if (out[i] < range[i][0]) { - out[i] = range[i][0]; - } else if (out[i] > range[i][1]) { - out[i] = range[i][1]; - } + k *= size_i; + pos <<= 1; } - return out; + + var y = new Float64Array(n); + for (var j = 0; j < n; ++j) { + // Sum all cube vertices' samples portions + var rj = 0; + for (var i = 0; i < cubeVertices; i++) + rj += samples[cubeVertex[i] + j] * cubeN[i]; + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, + // Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); + + // y_j = min(max(r_j, range_2j), range_2j+1) + y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + } + + return y; } }, - constructInterpolated: - function pdfFunctionConstructInterpolated(str, dict) { + constructInterpolated: function PDFFunction_constructInterpolated(str, + dict) { var c0 = dict.get('C0') || [0]; var c1 = dict.get('C1') || [1]; var n = dict.get('N'); @@ -249,7 +239,7 @@ var PDFFunction = (function PDFFunctionClosure() { }, constructInterpolatedFromIR: - function pdfFunctionconstructInterpolatedFromIR(IR) { + function PDFFunction_constructInterpolatedFromIR(IR) { var c0 = IR[1]; var diff = IR[2]; var n = IR[3]; @@ -268,7 +258,7 @@ var PDFFunction = (function PDFFunctionClosure() { } }, - constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) { + constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { var domain = dict.get('Domain'); if (!domain) @@ -278,18 +268,18 @@ var PDFFunction = (function PDFFunctionClosure() { if (inputSize != 1) error('Bad domain for stiched function'); - var fnRefs = xref.fetchIfRef(dict.get('Functions')); + var fnRefs = dict.get('Functions'); var fns = []; for (var i = 0, ii = fnRefs.length; i < ii; ++i) fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); - var bounds = xref.fetchIfRef(dict.get('Bounds')); - var encode = xref.fetchIfRef(dict.get('Encode')); + var bounds = dict.get('Bounds'); + var encode = dict.get('Encode'); return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; }, - constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) { + constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { var domain = IR[1]; var bounds = IR[2]; var encode = IR[3]; @@ -335,7 +325,7 @@ var PDFFunction = (function PDFFunctionClosure() { }; }, - constructPostScript: function pdfFunctionConstructPostScript(fn, dict, + constructPostScript: function PDFFunction_constructPostScript(fn, dict, xref) { var domain = dict.get('Domain'); var range = dict.get('Range'); @@ -353,8 +343,8 @@ var PDFFunction = (function PDFFunctionClosure() { return [CONSTRUCT_POSTSCRIPT, domain, range, code]; }, - constructPostScriptFromIR: - function pdfFunctionConstructPostScriptFromIR(IR) { + constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( + IR) { var domain = IR[1]; var range = IR[2]; var code = IR[3]; @@ -374,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; @@ -400,13 +390,13 @@ var FunctionCache = (function FunctionCacheClosure() { this.total = 0; } FunctionCache.prototype = { - has: function has(key) { + has: function FunctionCache_has(key) { return key in this.cache; }, - get: function get(key) { + get: function FunctionCache_get(key) { return this.cache[key]; }, - set: function set(key, value) { + set: function FunctionCache_set(key, value) { if (this.total < MAX_CACHE_SIZE) { this.cache[key] = value; this.total++; @@ -423,28 +413,28 @@ var PostScriptStack = (function PostScriptStackClosure() { } PostScriptStack.prototype = { - push: function push(value) { + push: function PostScriptStack_push(value) { if (this.stack.length >= MAX_STACK_SIZE) error('PostScript function stack overflow.'); this.stack.push(value); }, - pop: function pop() { + pop: function PostScriptStack_pop() { if (this.stack.length <= 0) error('PostScript function stack underflow.'); return this.stack.pop(); }, - copy: function copy(n) { + copy: function PostScriptStack_copy(n) { if (this.stack.length + n >= MAX_STACK_SIZE) error('PostScript function stack overflow.'); var stack = this.stack; for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) stack.push(stack[i]); }, - index: function index(n) { + index: function PostScriptStack_index(n) { this.push(this.stack[this.stack.length - n - 1]); }, // rotate the last n stack elements p times - roll: function roll(n, p) { + roll: function PostScriptStack_roll(n, p) { var stack = this.stack; var l = stack.length - n; var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; @@ -467,7 +457,7 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { this.operands = operands; } PostScriptEvaluator.prototype = { - execute: function execute(initialStack) { + execute: function PostScriptEvaluator_execute(initialStack) { var stack = new PostScriptStack(initialStack); var counter = 0; var operators = this.operators; @@ -701,31 +691,31 @@ var PostScriptParser = (function PostScriptParserClosure() { this.prev; } PostScriptParser.prototype = { - nextToken: function nextToken() { + nextToken: function PostScriptParser_nextToken() { this.prev = this.token; this.token = this.lexer.getToken(); }, - accept: function accept(type) { + accept: function PostScriptParser_accept(type) { if (this.token.type == type) { this.nextToken(); return true; } return false; }, - expect: function expect(type) { + expect: function PostScriptParser_expect(type) { if (this.accept(type)) return true; error('Unexpected symbol: found ' + this.token.type + ' expected ' + type + '.'); }, - parse: function parse() { + parse: function PostScriptParser_parse() { this.nextToken(); this.expect(PostScriptTokenTypes.LBRACE); this.parseBlock(); this.expect(PostScriptTokenTypes.RBRACE); return this.operators; }, - parseBlock: function parseBlock() { + parseBlock: function PostScriptParser_parseBlock() { while (true) { if (this.accept(PostScriptTokenTypes.NUMBER)) { this.operators.push(this.prev.value); @@ -738,7 +728,7 @@ var PostScriptParser = (function PostScriptParserClosure() { } } }, - parseCondition: function parseCondition() { + parseCondition: function PostScriptParser_parseCondition() { // Add two place holders that will be updated later var conditionLocation = this.operators.length; this.operators.push(null, null); @@ -789,7 +779,7 @@ var PostScriptToken = (function PostScriptTokenClosure() { var opCache = {}; - PostScriptToken.getOperator = function getOperator(op) { + PostScriptToken.getOperator = function PostScriptToken_getOperator(op) { var opValue = opCache[op]; if (opValue) return opValue; @@ -812,7 +802,7 @@ var PostScriptLexer = (function PostScriptLexerClosure() { this.stream = stream; } PostScriptLexer.prototype = { - getToken: function getToken() { + getToken: function PostScriptLexer_getToken() { var s = ''; var ch; var comment = false; @@ -862,7 +852,7 @@ var PostScriptLexer = (function PostScriptLexerClosure() { return PostScriptToken.getOperator(str); } }, - getNumber: function getNumber(ch) { + getNumber: function PostScriptLexer_getNumber(ch) { var str = ch; var stream = this.stream; while (true) { diff --git a/src/glyphlist.js b/src/glyphlist.js index 01b94442a..694134840 100644 --- a/src/glyphlist.js +++ b/src/glyphlist.js @@ -1508,27 +1508,7 @@ var GlyphsUnicode = { dalet: 0x05D3, daletdagesh: 0xFB33, daletdageshhebrew: 0xFB33, - dalethatafpatah: 0x05D305B2, - dalethatafpatahhebrew: 0x05D305B2, - dalethatafsegol: 0x05D305B1, - dalethatafsegolhebrew: 0x05D305B1, dalethebrew: 0x05D3, - dalethiriq: 0x05D305B4, - dalethiriqhebrew: 0x05D305B4, - daletholam: 0x05D305B9, - daletholamhebrew: 0x05D305B9, - daletpatah: 0x05D305B7, - daletpatahhebrew: 0x05D305B7, - daletqamats: 0x05D305B8, - daletqamatshebrew: 0x05D305B8, - daletqubuts: 0x05D305BB, - daletqubutshebrew: 0x05D305BB, - daletsegol: 0x05D305B6, - daletsegolhebrew: 0x05D305B6, - daletsheva: 0x05D305B0, - daletshevahebrew: 0x05D305B0, - dalettsere: 0x05D305B5, - dalettserehebrew: 0x05D305B5, dalfinalarabic: 0xFEAA, dammaarabic: 0x064F, dammalowarabic: 0x064F, @@ -1845,10 +1825,6 @@ var GlyphsUnicode = { finalkafdagesh: 0xFB3A, finalkafdageshhebrew: 0xFB3A, finalkafhebrew: 0x05DA, - finalkafqamats: 0x05DA05B8, - finalkafqamatshebrew: 0x05DA05B8, - finalkafsheva: 0x05DA05B0, - finalkafshevahebrew: 0x05DA05B0, finalmem: 0x05DD, finalmemhebrew: 0x05DD, finalnun: 0x05DF, @@ -2037,14 +2013,7 @@ var GlyphsUnicode = { hakatakanahalfwidth: 0xFF8A, halantgurmukhi: 0x0A4D, hamzaarabic: 0x0621, - hamzadammaarabic: 0x0621064F, - hamzadammatanarabic: 0x0621064C, - hamzafathaarabic: 0x0621064E, - hamzafathatanarabic: 0x0621064B, hamzalowarabic: 0x0621, - hamzalowkasraarabic: 0x06210650, - hamzalowkasratanarabic: 0x0621064D, - hamzasukunarabic: 0x06210652, hangulfiller: 0x3164, hardsigncyrillic: 0x044A, harpoonleftbarbup: 0x21BC, @@ -2476,10 +2445,6 @@ var GlyphsUnicode = { lameddagesh: 0xFB3C, lameddageshhebrew: 0xFB3C, lamedhebrew: 0x05DC, - lamedholam: 0x05DC05B9, - lamedholamdagesh: '05DC 05B9 05BC', - lamedholamdageshhebrew: '05DC 05B9 05BC', - lamedholamhebrew: 0x05DC05B9, lamfinalarabic: 0xFEDE, lamhahinitialarabic: 0xFCCA, laminitialarabic: 0xFEDF, @@ -2489,8 +2454,6 @@ var GlyphsUnicode = { lammedialarabic: 0xFEE0, lammeemhahinitialarabic: 0xFD88, lammeeminitialarabic: 0xFCCC, - lammeemjeeminitialarabic: 'FEDF FEE4 FEA0', - lammeemkhahinitialarabic: 'FEDF FEE4 FEA8', largecircle: 0x25EF, lbar: 0x019A, lbelt: 0x026C, @@ -2787,7 +2750,6 @@ var GlyphsUnicode = { noonfinalarabic: 0xFEE6, noonghunnaarabic: 0x06BA, noonghunnafinalarabic: 0xFB9F, - noonhehinitialarabic: 0xFEE7FEEC, nooninitialarabic: 0xFEE7, noonjeeminitialarabic: 0xFCD2, noonjeemisolatedarabic: 0xFC4B, @@ -3159,27 +3121,7 @@ var GlyphsUnicode = { qof: 0x05E7, qofdagesh: 0xFB47, qofdageshhebrew: 0xFB47, - qofhatafpatah: 0x05E705B2, - qofhatafpatahhebrew: 0x05E705B2, - qofhatafsegol: 0x05E705B1, - qofhatafsegolhebrew: 0x05E705B1, qofhebrew: 0x05E7, - qofhiriq: 0x05E705B4, - qofhiriqhebrew: 0x05E705B4, - qofholam: 0x05E705B9, - qofholamhebrew: 0x05E705B9, - qofpatah: 0x05E705B7, - qofpatahhebrew: 0x05E705B7, - qofqamats: 0x05E705B8, - qofqamatshebrew: 0x05E705B8, - qofqubuts: 0x05E705BB, - qofqubutshebrew: 0x05E705BB, - qofsegol: 0x05E705B6, - qofsegolhebrew: 0x05E705B6, - qofsheva: 0x05E705B0, - qofshevahebrew: 0x05E705B0, - qoftsere: 0x05E705B5, - qoftserehebrew: 0x05E705B5, qparen: 0x24AC, quarternote: 0x2669, qubuts: 0x05BB, @@ -3253,32 +3195,11 @@ var GlyphsUnicode = { reharmenian: 0x0580, rehfinalarabic: 0xFEAE, rehiragana: 0x308C, - rehyehaleflamarabic: '0631 FEF3 FE8E 0644', rekatakana: 0x30EC, rekatakanahalfwidth: 0xFF9A, resh: 0x05E8, reshdageshhebrew: 0xFB48, - reshhatafpatah: 0x05E805B2, - reshhatafpatahhebrew: 0x05E805B2, - reshhatafsegol: 0x05E805B1, - reshhatafsegolhebrew: 0x05E805B1, reshhebrew: 0x05E8, - reshhiriq: 0x05E805B4, - reshhiriqhebrew: 0x05E805B4, - reshholam: 0x05E805B9, - reshholamhebrew: 0x05E805B9, - reshpatah: 0x05E805B7, - reshpatahhebrew: 0x05E805B7, - reshqamats: 0x05E805B8, - reshqamatshebrew: 0x05E805B8, - reshqubuts: 0x05E805BB, - reshqubutshebrew: 0x05E805BB, - reshsegol: 0x05E805B6, - reshsegolhebrew: 0x05E805B6, - reshsheva: 0x05E805B0, - reshshevahebrew: 0x05E805B0, - reshtsere: 0x05E805B5, - reshtserehebrew: 0x05E805B5, reversedtilde: 0x223D, reviahebrew: 0x0597, reviamugrashhebrew: 0x0597, @@ -3477,7 +3398,6 @@ var GlyphsUnicode = { shaddadammaarabic: 0xFC61, shaddadammatanarabic: 0xFC5E, shaddafathaarabic: 0xFC60, - shaddafathatanarabic: 0x0651064B, shaddakasraarabic: 0xFC62, shaddakasratanarabic: 0xFC5F, shade: 0x2592, @@ -3674,7 +3594,6 @@ var GlyphsUnicode = { tchehfinalarabic: 0xFB7B, tchehinitialarabic: 0xFB7C, tchehmedialarabic: 0xFB7D, - tchehmeeminitialarabic: 0xFB7CFEE4, tcircle: 0x24E3, tcircumflexbelow: 0x1E71, tcommaaccent: 0x0163, diff --git a/src/image.js b/src/image.js index 6e7ab2020..035e2f754 100644 --- a/src/image.js +++ b/src/image.js @@ -94,7 +94,7 @@ var PDFImage = (function PDFImageClosure() { } } - var mask = xref.fetchIfRef(dict.get('Mask')); + var mask = dict.get('Mask'); if (mask) { TODO('masked images'); @@ -106,8 +106,8 @@ var PDFImage = (function PDFImageClosure() { * Handles processing of image data and calls the callback with an argument * of a PDFImage when the image is ready to be used. */ - PDFImage.buildImage = function buildImage(callback, handler, xref, res, - image, inline) { + PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref, + res, image, inline) { var imageDataPromise = new Promise(); var smaskPromise = new Promise(); // The image data and smask data may not be ready yet, wait till both are @@ -120,7 +120,7 @@ var PDFImage = (function PDFImageClosure() { handleImageData(handler, xref, res, image, imageDataPromise); - var smask = xref.fetchIfRef(image.dict.get('SMask')); + var smask = image.dict.get('SMask'); if (smask) handleImageData(handler, xref, res, smask, smaskPromise); else @@ -139,7 +139,8 @@ var PDFImage = (function PDFImageClosure() { * @param {Number} h2 New height. * @return {TypedArray} Resized image data. */ - PDFImage.resize = function resize(pixels, bpc, components, w1, h1, w2, h2) { + PDFImage.resize = function PDFImage_resize(pixels, bpc, components, + w1, h1, w2, h2) { var length = w2 * h2 * components; var temp = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); @@ -177,7 +178,7 @@ var PDFImage = (function PDFImageClosure() { return this.height; return Math.max(this.height, this.smask.height); }, - getComponents: function getComponents(buffer) { + getComponents: function PDFImage_getComponents(buffer) { var bpc = this.bpc; var needsDecode = this.needsDecode; var decodeMap = this.decode; @@ -265,7 +266,7 @@ var PDFImage = (function PDFImageClosure() { } return output; }, - getOpacity: function getOpacity(width, height) { + getOpacity: function PDFImage_getOpacity(width, height) { var smask = this.smask; var originalWidth = this.width; var originalHeight = this.height; @@ -285,7 +286,8 @@ var PDFImage = (function PDFImageClosure() { } return buf; }, - applyStencilMask: function applyStencilMask(buffer, inverseDecode) { + applyStencilMask: function PDFImage_applyStencilMask(buffer, + inverseDecode) { var width = this.width, height = this.height; var bitStrideLength = (width + 7) >> 3; var imgArray = this.getImageBytes(bitStrideLength * height); @@ -308,7 +310,7 @@ var PDFImage = (function PDFImageClosure() { } } }, - fillRgbaBuffer: function fillRgbaBuffer(buffer, width, height) { + fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) { var numComps = this.numComps; var originalWidth = this.width; var originalHeight = this.height; @@ -335,7 +337,7 @@ var PDFImage = (function PDFImageClosure() { buffer[i + 3] = opacity[opacityPos++]; } }, - fillGrayBuffer: function fillGrayBuffer(buffer) { + fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) { var numComps = this.numComps; if (numComps != 1) error('Reading gray scale from a color image: ' + numComps); @@ -355,7 +357,7 @@ var PDFImage = (function PDFImageClosure() { for (var i = 0; i < length; ++i) buffer[i] = (scale * comps[i]) | 0; }, - getImageBytes: function getImageBytes(length) { + getImageBytes: function PDFImage_getImageBytes(length) { this.image.reset(); return this.image.getBytes(length); } @@ -365,7 +367,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 61a8f4487..63193753d 100644 --- a/src/jpx.js +++ b/src/jpx.js @@ -4,6 +4,319 @@ 'use strict'; var JpxImage = (function JpxImageClosure() { + // Table E.1 + var SubbandsGainLog2 = { + 'LL': 0, + 'LH': 1, + 'HL': 1, + 'HH': 2 + }; + function JpxImage() { + this.failOnCorruptedImage = false; + } + JpxImage.prototype = { + load: function JpxImage_load(url) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = (function() { + // TODO catch parse error + var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); + this.parse(data); + if (this.onload) + this.onload(); + }).bind(this); + xhr.send(null); + }, + parse: function JpxImage_parse(data) { + function ReadUint(data, offset, bytes) { + var n = 0; + for (var i = 0; i < bytes; i++) + n = n * 256 + (data[offset + i] & 0xFF); + return n; + } + var position = 0, length = data.length; + while (position < length) { + var headerSize = 8; + var lbox = ReadUint(data, position, 4); + var tbox = ReadUint(data, position + 4, 4); + position += headerSize; + if (lbox == 1) { + lbox = ReadUint(data, position, 8); + position += 8; + headerSize += 8; + } + if (lbox == 0) + lbox = length - position + headerSize; + if (lbox < headerSize) + error('JPX error: Invalid box field size'); + var dataLength = lbox - headerSize; + var jumpDataLength = true; + switch (tbox) { + case 0x6A501A1A: // 'jP\032\032' + // TODO + break; + case 0x6A703268: // 'jp2h' + jumpDataLength = false; // parsing child boxes + break; + case 0x636F6C72: // 'colr' + // TODO + break; + case 0x6A703263: // 'jp2c' + this.parseCodestream(data, position, position + dataLength); + break; + } + if (jumpDataLength) + position += dataLength; + } + }, + parseCodestream: function JpxImage_parseCodestream(data, start, end) { + var context = {}; + try { + var position = start; + while (position < end) { + var code = readUint16(data, position); + position += 2; + + var length = 0, j; + switch (code) { + case 0xFF4F: // Start of codestream (SOC) + context.mainHeader = true; + break; + case 0xFFD9: // End of codestream (EOC) + break; + case 0xFF51: // Image and tile size (SIZ) + length = readUint16(data, position); + var siz = {}; + siz.Xsiz = readUint32(data, position + 4); + siz.Ysiz = readUint32(data, position + 8); + siz.XOsiz = readUint32(data, position + 12); + siz.YOsiz = readUint32(data, position + 16); + siz.XTsiz = readUint32(data, position + 20); + siz.YTsiz = readUint32(data, position + 24); + siz.XTOsiz = readUint32(data, position + 28); + siz.YTOsiz = readUint32(data, position + 32); + var componentsCount = readUint16(data, position + 36); + siz.Csiz = componentsCount; + var components = []; + j = position + 38; + for (var i = 0; i < componentsCount; i++) { + var component = { + precision: (data[j] & 0x7F) + 1, + isSigned: !!(data[j] & 0x80), + XRsiz: data[j + 1], + YRsiz: data[j + 1] + }; + calculateComponentDimensions(component, siz); + components.push(component); + } + context.SIZ = siz; + context.components = components; + calculateTileGrids(context, components); + context.QCC = []; + context.COC = []; + break; + case 0xFF5C: // Quantization default (QCD) + length = readUint16(data, position); + var qcd = {}; + j = position + 2; + var sqcd = data[j++]; + var spqcdSize, scalarExpounded; + switch (sqcd & 0x1F) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw 'Invalid SQcd value ' + sqcd; + } + qcd.noQuantization = spqcdSize == 8; + qcd.scalarExpounded = scalarExpounded; + qcd.guardBits = sqcd >> 5; + var spqcds = []; + while (j < length + position) { + var spqcd = {}; + if (spqcdSize == 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; + } else { + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; + j += 2; + } + spqcds.push(spqcd); + } + qcd.SPqcds = spqcds; + if (context.mainHeader) + context.QCD = qcd; + else { + context.currentTile.QCD = qcd; + context.currentTile.QCC = []; + } + break; + case 0xFF5D: // Quantization component (QCC) + length = readUint16(data, position); + var qcc = {}; + j = position + 2; + var cqcc; + if (context.SIZ.Csiz < 257) + cqcc = data[j++]; + else { + cqcc = readUint16(data, j); + j += 2; + } + var sqcd = data[j++]; + var spqcdSize, scalarExpounded; + switch (sqcd & 0x1F) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw 'Invalid SQcd value ' + sqcd; + } + qcc.noQuantization = spqcdSize == 8; + qcc.scalarExpounded = scalarExpounded; + qcc.guardBits = sqcd >> 5; + var spqcds = []; + while (j < length + position) { + var spqcd = {}; + if (spqcdSize == 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; + } else { + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; + j += 2; + } + spqcds.push(spqcd); + } + qcc.SPqcds = spqcds; + if (context.mainHeader) + context.QCC[cqcc] = qcc; + else + context.currentTile.QCC[cqcc] = qcc; + break; + case 0xFF52: // Coding style default (COD) + length = readUint16(data, position); + var cod = {}; + j = position + 2; + var scod = data[j++]; + cod.entropyCoderWithCustomPrecincts = !!(scod & 1); + cod.sopMarkerUsed = !!(scod & 2); + cod.ephMarkerUsed = !!(scod & 4); + var codingStyle = {}; + cod.progressionOrder = data[j++]; + cod.layersCount = readUint16(data, j); + j += 2; + cod.multipleComponentTransform = data[j++]; + + cod.decompositionLevelsCount = data[j++]; + cod.xcb = (data[j++] & 0xF) + 2; + cod.ycb = (data[j++] & 0xF) + 2; + var blockStyle = data[j++]; + cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); + cod.resetContextProbabilities = !!(blockStyle & 2); + cod.terminationOnEachCodingPass = !!(blockStyle & 4); + cod.verticalyStripe = !!(blockStyle & 8); + cod.predictableTermination = !!(blockStyle & 16); + cod.segmentationSymbolUsed = !!(blockStyle & 32); + cod.transformation = data[j++]; + if (cod.entropyCoderWithCustomPrecincts) { + var precinctsSizes = {}; + while (j < length + position) { + var precinctsSize = data[j]; + precinctsSizes.push({ + PPx: precinctsSize & 0xF, + PPy: precinctsSize >> 4 + }); + } + cod.precinctsSizes = precinctsSizes; + } + + if (cod.sopMarkerUsed || cod.ephMarkerUsed || + cod.selectiveArithmeticCodingBypass || + cod.resetContextProbabilities || + cod.terminationOnEachCodingPass || + cod.verticalyStripe || cod.predictableTermination || + cod.segmentationSymbolUsed) + throw 'Unsupported COD options: ' + uneval(cod); + + if (context.mainHeader) + context.COD = cod; + else { + context.currentTile.COD = cod; + context.currentTile.COC = []; + } + break; + case 0xFF90: // Start of tile-part (SOT) + length = readUint16(data, position); + var tile = {}; + tile.index = readUint16(data, position + 2); + tile.length = readUint32(data, position + 4); + tile.dataEnd = tile.length + position - 2; + tile.partIndex = data[position + 8]; + tile.partsCount = data[position + 9]; + + context.mainHeader = false; + if (tile.partIndex == 0) { + // reset component specific settings + tile.COD = context.COD; + tile.COC = context.COC.slice(0); // clone of the global COC + tile.QCD = context.QCD; + tile.QCC = context.QCC.slice(0); // clone of the global COC + } + context.currentTile = tile; + break; + case 0xFF93: // Start of data (SOD) + var tile = context.currentTile; + if (tile.partIndex == 0) { + initializeTile(context, tile.index); + buildPackets(context); + } + + // moving to the end of the data + length = tile.dataEnd - position; + + parseTilePackets(context, data, position, length); + break; + case 0xFF64: // Comment (COM) + length = readUint16(data, position); + // skipping content + break; + default: + throw 'Unknown codestream code: ' + code.toString(16); + } + position += length; + } + } catch (e) { + if (this.failOnCorruptedImage) + error('JPX error: ' + e); + else + warn('JPX error: ' + e + '. Trying to recover'); + } + this.tiles = transformComponents(context); + this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; + this.height = context.SIZ.Ysiz - context.SIZ.YOsiz; + this.componentsCount = context.SIZ.Csiz; + } + }; function readUint32(data, offset) { return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; @@ -19,821 +332,6 @@ var JpxImage = (function JpxImageClosure() { } return i; } - - // Section B.10.2 Tag trees - var TagTree = (function TagTreeClosure() { - function TagTree(width, height) { - var levelsLength = log2(Math.max(width, height)) + 1; - this.levels = []; - for (var i = 0; i < levelsLength; i++) { - var level = { - width: width, - height: height, - items: [] - }; - this.levels.push(level); - width = Math.ceil(width / 2); - height = Math.ceil(height / 2); - } - } - TagTree.prototype = { - reset: function tagTreeReset(i, j) { - var currentLevel = 0, value = 0; - while (currentLevel < this.levels.length) { - var level = this.levels[currentLevel]; - var index = i + j * level.width; - if (index in level.items) { - value = level.items[index]; - break; - } - level.index = index; - i >>= 1; - j >>= 1; - currentLevel++; - } - currentLevel--; - var level = this.levels[currentLevel]; - level.items[level.index] = value; - this.currentLevel = currentLevel; - delete this.value; - }, - incrementValue: function tagTreeIncrementValue() { - var level = this.levels[this.currentLevel]; - level.items[level.index]++; - }, - nextLevel: function tagTreeNextLevel() { - var currentLevel = this.currentLevel; - var level = this.levels[currentLevel]; - var value = level.items[level.index]; - currentLevel--; - if (currentLevel < 0) { - this.value = value; - return false; - } - - this.currentLevel = currentLevel; - var level = this.levels[currentLevel]; - level.items[level.index] = value; - return true; - } - }; - return TagTree; - })(); - - var InclusionTree = (function InclusionTreeClosure() { - function InclusionTree(width, height, defaultValue) { - var levelsLength = log2(Math.max(width, height)) + 1; - this.levels = []; - for (var i = 0; i < levelsLength; i++) { - var items = new Uint8Array(width * height); - for (var j = 0, jj = items.length; j < jj; j++) - items[j] = defaultValue; - - var level = { - width: width, - height: height, - items: items - }; - this.levels.push(level); - - width = Math.ceil(width / 2); - height = Math.ceil(height / 2); - } - } - InclusionTree.prototype = { - reset: function inclusionTreeReset(i, j, stopValue) { - var currentLevel = 0; - while (currentLevel < this.levels.length) { - var level = this.levels[currentLevel]; - var index = i + j * level.width; - level.index = index; - var value = level.items[index]; - - if (value == 0xFF) - break; - - if (value > stopValue) { - this.currentLevel = currentLevel; - // already know about this one, propagating the value to top levels - this.propagateValues(); - return false; - } - - i >>= 1; - j >>= 1; - currentLevel++; - } - this.currentLevel = currentLevel - 1; - return true; - }, - incrementValue: function inclusionTreeIncrementValue(stopValue) { - var level = this.levels[this.currentLevel]; - level.items[level.index] = stopValue + 1; - this.propagateValues(); - }, - propagateValues: function inclusionTreePropagateValues() { - var levelIndex = this.currentLevel; - var level = this.levels[levelIndex]; - var currentValue = level.items[level.index]; - while (--levelIndex >= 0) { - var level = this.levels[levelIndex]; - level.items[level.index] = currentValue; - } - }, - nextLevel: function inclusionTreeNextLevel() { - var currentLevel = this.currentLevel; - var level = this.levels[currentLevel]; - var value = level.items[level.index]; - level.items[level.index] = 0xFF; - currentLevel--; - if (currentLevel < 0) - return false; - - this.currentLevel = currentLevel; - var level = this.levels[currentLevel]; - level.items[level.index] = value; - return true; - } - }; - return InclusionTree; - })(); - - // Implements C.3. Arithmetic decoding procedures - var ArithmeticDecoder = (function arithmeticDecoderClosure() { - var QeTable = [ - {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, - {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, - {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0}, - {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0}, - {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0}, - {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0}, - {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1}, - {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0}, - {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0}, - {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0}, - {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0}, - {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0}, - {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0}, - {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0}, - {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1}, - {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0}, - {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0}, - {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0}, - {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0}, - {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0}, - {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0}, - {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0}, - {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0}, - {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0}, - {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0}, - {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0}, - {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0}, - {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0}, - {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0}, - {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0}, - {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0}, - {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0}, - {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0}, - {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0}, - {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0}, - {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0}, - {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0}, - {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0}, - {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0}, - {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0}, - {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0}, - {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0}, - {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0}, - {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0}, - {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0}, - {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0}, - {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0} - ]; - - function ArithmeticDecoder(data, start, end) { - this.data = data; - this.bp = start; - this.dataEnd = end; - - this.chigh = data[start]; - this.clow = 0; - - this.byteIn(); - - this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F); - this.clow = (this.clow << 7) & 0xFFFF; - this.ct -= 7; - this.a = 0x8000; - } - - ArithmeticDecoder.prototype = { - byteIn: function arithmeticDecoderByteIn() { - var data = this.data; - var bp = this.bp; - if (data[bp] == 0xFF) { - var b1 = data[bp + 1]; - if (b1 > 0x8F) { - this.clow += 0xFF00; - this.ct = 8; - } else { - bp++; - this.clow += (data[bp] << 9); - this.ct = 7; - this.bp = bp; - } - } else { - bp++; - this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00; - this.ct = 8; - this.bp = bp; - } - if (this.clow > 0xFFFF) { - this.chigh += (this.clow >> 16); - this.clow &= 0xFFFF; - } - }, - readBit: function arithmeticDecoderReadBit(cx) { - var qeIcx = QeTable[cx.index].qe; - this.a -= qeIcx; - - if (this.chigh < qeIcx) { - var d = this.exchangeLps(cx); - this.renormD(); - return d; - } else { - this.chigh -= qeIcx; - if ((this.a & 0x8000) == 0) { - var d = this.exchangeMps(cx); - this.renormD(); - return d; - } else { - return cx.mps; - } - } - }, - renormD: function arithmeticDecoderRenormD() { - do { - if (this.ct == 0) - this.byteIn(); - - this.a <<= 1; - this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); - this.clow = (this.clow << 1) & 0xFFFF; - this.ct--; - } while ((this.a & 0x8000) == 0); - }, - exchangeMps: function arithmeticDecoderExchangeMps(cx) { - var d; - var qeTableIcx = QeTable[cx.index]; - if (this.a < qeTableIcx.qe) { - d = 1 - cx.mps; - - if (qeTableIcx.switchFlag == 1) { - cx.mps = 1 - cx.mps; - } - cx.index = qeTableIcx.nlps; - } else { - d = cx.mps; - cx.index = qeTableIcx.nmps; - } - return d; - }, - exchangeLps: function arithmeticDecoderExchangeMps(cx) { - var d; - var qeTableIcx = QeTable[cx.index]; - if (this.a < qeTableIcx.qe) { - this.a = qeTableIcx.qe; - d = cx.mps; - cx.index = qeTableIcx.nmps; - } else { - this.a = qeTableIcx.qe; - d = 1 - cx.mps; - - if (qeTableIcx.switchFlag == 1) { - cx.mps = 1 - cx.mps; - } - cx.index = qeTableIcx.nlps; - } - return d; - } - }; - - return ArithmeticDecoder; - })(); - - // Section D. Coefficient bit modeling - var BitModel = (function BitModelClosure() { - // Table D-1 - // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), - // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) - var LLAndLHContextsLabel = new Uint8Array([ - 0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, - 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, - 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8 - ]); - var HLContextLabel = new Uint8Array([ - 0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, - 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, - 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8 - ]); - var HHContextLabel = new Uint8Array([ - 0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, - 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, - 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8 - ]); - - // Table D-2 - function calcSignContribution(significance0, sign0, significance1, sign1) { - if (significance1) { - if (!sign1) - return significance0 ? (!sign0 ? 1 : 0) : 1; - else - return significance0 ? (!sign0 ? 0 : -1) : -1; - } else - return significance0 ? (!sign0 ? 1 : -1) : 0; - } - // Table D-3 - var SignContextLabels = [ - {contextLabel: 13, xorBit: 0}, - {contextLabel: 12, xorBit: 0}, - {contextLabel: 11, xorBit: 0}, - {contextLabel: 10, xorBit: 0}, - {contextLabel: 9, xorBit: 0}, - {contextLabel: 10, xorBit: 1}, - {contextLabel: 11, xorBit: 1}, - {contextLabel: 12, xorBit: 1}, - {contextLabel: 13, xorBit: 1} - ]; - - function BitModel(width, height, subband, zeroBitPlanes) { - this.width = width; - this.height = height; - - this.contextLabelTable = subband == 'HH' ? HHContextLabel : - subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel; - - var coefficientCount = width * height; - - // coefficients outside the encoding region treated as insignificant - // add border state cells for significanceState - this.neighborsSignificance = new Uint8Array(coefficientCount); - this.coefficentsSign = new Uint8Array(coefficientCount); - this.coefficentsMagnitude = new Uint32Array(coefficientCount); - this.processingFlags = new Uint8Array(coefficientCount); - - var bitsDecoded = new Uint8Array(this.width * this.height); - for (var i = 0, ii = bitsDecoded.length; i < ii; i++) - bitsDecoded[i] = zeroBitPlanes; - this.bitsDecoded = bitsDecoded; - - this.reset(); - } - - BitModel.prototype = { - setDecoder: function bitModelSetDecoder(decoder) { - this.decoder = decoder; - }, - reset: function bitModelReset() { - this.uniformContext = {index: 46, mps: 0}; - this.runLengthContext = {index: 3, mps: 0}; - this.contexts = []; - this.contexts.push({index: 4, mps: 0}); - for (var i = 1; i <= 16; i++) - this.contexts.push({index: 0, mps: 0}); - }, - setNeighborsSignificance: - function bitModelSetNeighborsSignificance(row, column) { - var neighborsSignificance = this.neighborsSignificance; - var width = this.width, height = this.height; - var index = row * width + column; - if (row > 0) { - if (column > 0) - neighborsSignificance[index - width - 1] += 0x10; - if (column + 1 < width) - neighborsSignificance[index - width + 1] += 0x10; - neighborsSignificance[index - width] += 0x04; - } - if (row + 1 < height) { - if (column > 0) - neighborsSignificance[index + width - 1] += 0x10; - if (column + 1 < width) - neighborsSignificance[index + width + 1] += 0x10; - neighborsSignificance[index + width] += 0x04; - } - if (column > 0) - neighborsSignificance[index - 1] += 0x01; - if (column + 1 < width) - neighborsSignificance[index + 1] += 0x01; - neighborsSignificance[index] |= 0x80; - }, - runSignificancePropogationPass: - function bitModelRunSignificancePropogationPass() { - var decoder = this.decoder; - var width = this.width, height = this.height; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var contextLabels = this.contextLabels; - var neighborsSignificance = this.neighborsSignificance; - var processingFlags = this.processingFlags; - var contexts = this.contexts; - var labels = this.contextLabelTable; - var bitsDecoded = this.bitsDecoded; - // clear processed flag - var processedInverseMask = ~1; - var processedMask = 1; - var firstMagnitudeBitMask = 2; - for (var q = 0, qq = width * height; q < qq; q++) - processingFlags[q] &= processedInverseMask; - - for (var i0 = 0; i0 < height; i0 += 4) { - for (var j = 0; j < width; j++) { - var index = i0 * width + j; - for (var i1 = 0; i1 < 4; i1++, index += width) { - var i = i0 + i1; - if (i >= height) - break; - - if (coefficentsMagnitude[index] || !neighborsSignificance[index]) - continue; - - var contextLabel = labels[neighborsSignificance[index]]; - var cx = contexts[contextLabel]; - var decision = decoder.readBit(cx); - if (decision) { - var sign = this.decodeSignBit(i, j); - coefficentsSign[index] = sign; - coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); - processingFlags[index] |= firstMagnitudeBitMask; - } - bitsDecoded[index]++; - processingFlags[index] |= processedMask; - } - } - } - }, - decodeSignBit: function bitModelDecodeSignBit(row, column) { - var width = this.width, height = this.height; - var index = row * width + column; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var horizontalContribution = calcSignContribution( - column > 0 && coefficentsMagnitude[index - 1], - coefficentsSign[index - 1], - column + 1 < width && coefficentsMagnitude[index + 1], - coefficentsSign[index + 1]); - var verticalContribution = calcSignContribution( - row > 0 && coefficentsMagnitude[index - width], - coefficentsSign[index - width], - row + 1 < height && coefficentsMagnitude[index + width], - coefficentsSign[index + width]); - - var contextLabelAndXor = SignContextLabels[ - 3 * (1 - horizontalContribution) + (1 - verticalContribution)]; - var contextLabel = contextLabelAndXor.contextLabel; - var cx = this.contexts[contextLabel]; - var decoded = this.decoder.readBit(cx); - return decoded ^ contextLabelAndXor.xorBit; - }, - runMagnitudeRefinementPass: - function bitModelRunMagnitudeRefinementPass() { - var decoder = this.decoder; - var width = this.width, height = this.height; - var coefficentsMagnitude = this.coefficentsMagnitude; - var neighborsSignificance = this.neighborsSignificance; - var contexts = this.contexts; - var bitsDecoded = this.bitsDecoded; - var processingFlags = this.processingFlags; - var processedMask = 1; - var firstMagnitudeBitMask = 2; - for (var i0 = 0; i0 < height; i0 += 4) { - for (var j = 0; j < width; j++) { - for (var i1 = 0; i1 < 4; i1++) { - var i = i0 + i1; - if (i >= height) - break; - var index = i * width + j; - - // significant but not those that have just become - if (!coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) != 0) - continue; - - var contextLabel = 16; - if ((processingFlags[index] & - firstMagnitudeBitMask) != 0) { - processingFlags[i * width + j] ^= firstMagnitudeBitMask; - // first refinement - var significance = neighborsSignificance[index]; - var sumOfSignificance = (significance & 3) + - ((significance >> 2) & 3) + ((significance >> 4) & 7); - contextLabel = sumOfSignificance >= 1 ? 15 : 14; - } - - var cx = contexts[contextLabel]; - var bit = decoder.readBit(cx); - coefficentsMagnitude[index] = - (coefficentsMagnitude[index] << 1) | bit; - bitsDecoded[index]++; - processingFlags[index] |= processedMask; - } - } - } - }, - runCleanupPass: function bitModelRunCleanupPass() { - var decoder = this.decoder; - var width = this.width, height = this.height; - var neighborsSignificance = this.neighborsSignificance; - var significanceState = this.significanceState; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var contexts = this.contexts; - var labels = this.contextLabelTable; - var bitsDecoded = this.bitsDecoded; - var processingFlags = this.processingFlags; - var processedMask = 1; - var firstMagnitudeBitMask = 2; - var oneRowDown = width; - var twoRowsDown = width * 2; - var threeRowsDown = width * 3; - for (var i0 = 0; i0 < height; i0 += 4) { - for (var j = 0; j < width; j++) { - var index0 = i0 * width + j; - // using the property: labels[neighborsSignificance[index]] == 0 - // when neighborsSignificance[index] == 0 - var allEmpty = i0 + 3 < height && - processingFlags[index0] == 0 && - processingFlags[index0 + oneRowDown] == 0 && - processingFlags[index0 + twoRowsDown] == 0 && - processingFlags[index0 + threeRowsDown] == 0 && - neighborsSignificance[index0] == 0 && - neighborsSignificance[index0 + oneRowDown] == 0 && - neighborsSignificance[index0 + twoRowsDown] == 0 && - neighborsSignificance[index0 + threeRowsDown] == 0; - var i1 = 0, index = index0; - var cx, i; - if (allEmpty) { - cx = this.runLengthContext; - var hasSignificantCoefficent = decoder.readBit(cx); - if (!hasSignificantCoefficent) { - bitsDecoded[index0]++; - bitsDecoded[index0 + oneRowDown]++; - bitsDecoded[index0 + twoRowsDown]++; - bitsDecoded[index0 + threeRowsDown]++; - continue; // next column - } - cx = this.uniformContext; - i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx); - i = i0 + i1; - index += i1 * width; - - var sign = this.decodeSignBit(i, j); - coefficentsSign[index] = sign; - coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); - processingFlags[index] |= firstMagnitudeBitMask; - - index = index0; - for (var i2 = i0; i2 <= i; i2++, index += width) - bitsDecoded[index]++; - - i1++; - } - for (; i1 < 4; i1++, index += width) { - i = i0 + i1; - if (i >= height) - break; - - if (coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) != 0) - continue; - - var contextLabel = labels[neighborsSignificance[index]]; - cx = contexts[contextLabel]; - var decision = decoder.readBit(cx); - if (decision == 1) { - var sign = this.decodeSignBit(i, j); - coefficentsSign[index] = sign; - coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j); - processingFlags[index] |= firstMagnitudeBitMask; - } - bitsDecoded[index]++; - } - } - } - } - }; - - return BitModel; - })(); - - // Section F, Discrete wavelet transofrmation - var Transform = (function TransformClosure() { - function Transform() { - } - Transform.prototype.calculate = - function transformCalculate(subbands, u0, v0) { - var ll = subbands[0]; - for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) { - ll = this.iterate(ll, subbands[i], subbands[i + 1], - subbands[i + 2], u0, v0); - } - return ll; - }; - Transform.prototype.iterate = function transformIterate(ll, hl, lh, hh, - u0, v0) { - var llWidth = ll.width, llHeight = ll.height, llItems = ll.items; - var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items; - var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items; - var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items; - - // Section F.3.3 interleave - var width = llWidth + hlWidth; - var height = llHeight + lhHeight; - var items = new Float32Array(width * height); - for (var i = 0, ii = llHeight; i < ii; i++) { - var k = i * llWidth, l = i * 2 * width; - for (var j = 0, jj = llWidth; j < jj; j++, k++, l += 2) - items[l] = llItems[k]; - } - for (var i = 0, ii = hlHeight; i < ii; i++) { - var k = i * hlWidth, l = i * 2 * width + 1; - for (var j = 0, jj = hlWidth; j < jj; j++, k++, l += 2) - items[l] = hlItems[k]; - } - for (var i = 0, ii = lhHeight; i < ii; i++) { - var k = i * lhWidth, l = (i * 2 + 1) * width; - for (var j = 0, jj = lhWidth; j < jj; j++, k++, l += 2) - items[l] = lhItems[k]; - } - for (var i = 0, ii = hhHeight; i < ii; i++) { - var k = i * hhWidth, l = (i * 2 + 1) * width + 1; - for (var j = 0, jj = hhWidth; j < jj; j++, k++, l += 2) - items[l] = hhItems[k]; - } - - var bufferPadding = 4; - var bufferLength = new Float32Array(Math.max(width, height) + - 2 * bufferPadding); - var buffer = new Float32Array(bufferLength); - var bufferOut = new Float32Array(bufferLength); - - // Section F.3.4 HOR_SR - for (var v = 0; v < height; v++) { - if (width == 1) { - // if width = 1, when u0 even keep items as is, when odd divide by 2 - if ((u0 % 1) != 0) { - items[v * width] /= 2; - } - continue; - } - - var k = v * width; - var l = bufferPadding; - for (var u = 0; u < width; u++, k++, l++) - buffer[l] = items[k]; - - // Section F.3.7 extending... using max extension of 4 - var i1 = bufferPadding - 1, j1 = bufferPadding + 1; - var i2 = bufferPadding + width - 2, j2 = bufferPadding + width; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - - this.filter(buffer, bufferPadding, width, u0, bufferOut); - - k = v * width; - l = bufferPadding; - for (var u = 0; u < width; u++, k++, l++) - items[k] = bufferOut[l]; - } - - // Section F.3.5 VER_SR - for (var u = 0; u < width; u++) { - if (height == 1) { - // if height = 1, when v0 even keep items as is, when odd divide by 2 - if ((v0 % 1) != 0) { - items[u] /= 2; - } - continue; - } - - var k = u; - var l = bufferPadding; - for (var v = 0; v < height; v++, k += width, l++) - buffer[l] = items[k]; - - // Section F.3.7 extending... using max extension of 4 - var i1 = bufferPadding - 1, j1 = bufferPadding + 1; - var i2 = bufferPadding + height - 2, j2 = bufferPadding + height; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - - this.filter(buffer, bufferPadding, height, v0, bufferOut); - - k = u; - l = bufferPadding; - for (var v = 0; v < height; v++, k += width, l++) - items[k] = bufferOut[l]; - } - return { - width: width, - height: height, - items: items - }; - }; - return Transform; - })(); - - // Section 3.8.2 Irreversible 9-7 filter - var IrreversibleTransform = (function IrreversibleTransformClosure() { - function IrreversibleTransform() { - Transform.call(this); - } - - IrreversibleTransform.prototype = Object.create(Transform.prototype); - IrreversibleTransform.prototype.filter = - function irreversibleTransformFilter(y, offset, length, i0, x) { - var i0_ = Math.floor(i0 / 2); - var i1_ = Math.floor((i0 + length) / 2); - var offset_ = offset - (i0 % 1); - - var alpha = -1.586134342059924; - var beta = -0.052980118572961; - var gamma = 0.882911075530934; - var delta = 0.443506852043971; - var K = 1.230174104914001; - var K_ = 1 / K; - - // step 1 - var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) - x[j] = K * y[j]; - - // step 2 - var j = offset_ - 3; - for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) - x[j] = K_ * y[j]; - - // step 3 - var j = offset_ - 2; - for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) - x[j] -= delta * (x[j - 1] + x[j + 1]); - - // step 4 - var j = offset_ - 1; - for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) - x[j] -= gamma * (x[j - 1] + x[j + 1]); - - // step 5 - var j = offset_; - for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) - x[j] -= beta * (x[j - 1] + x[j + 1]); - - // step 6 - var j = offset_ + 1; - for (var n = i0_, nn = i1_; n < nn; n++, j += 2) - x[j] -= alpha * (x[j - 1] + x[j + 1]); - }; - - return IrreversibleTransform; - })(); - - // Section 3.8.1 Reversible 5-3 filter - var ReversibleTransform = (function ReversibleTransformClosure() { - function ReversibleTransform() { - Transform.call(this); - } - - ReversibleTransform.prototype = Object.create(Transform.prototype); - ReversibleTransform.prototype.filter = - function reversibleTransformFilter(y, offset, length, i0, x) { - var i0_ = Math.floor(i0 / 2); - var i1_ = Math.floor((i0 + length) / 2); - var offset_ = offset - (i0 % 1); - - for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) - x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4); - - for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) - x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2); - }; - - return ReversibleTransform; - })(); - function calculateComponentDimensions(component, siz) { // Section B.2 Component mapping component.x0 = Math.ceil(siz.XOsiz / component.XRsiz); @@ -1030,7 +528,7 @@ var JpxImage = (function JpxImageClosure() { var l = 0, r = 0, i = 0, k = 0; - this.nextPacket = function progressionOrder0NextPacket() { + this.nextPacket = function JpxImage_nextPacket() { // Section B.12.1.1 Layer-resolution-component-position for (; l < layersCount; l++) { for (; r <= maxDecompositionLevelsCount; r++) { @@ -1069,7 +567,7 @@ var JpxImage = (function JpxImageClosure() { var r = 0, l = 0, i = 0, k = 0; - this.nextPacket = function progressionOrder1NextPacket() { + this.nextPacket = function JpxImage_nextPacket() { // Section B.12.1.2 Resolution-layer-component-position for (; r <= maxDecompositionLevelsCount; r++) { for (; l < layersCount; l++) { @@ -1187,7 +685,7 @@ var JpxImage = (function JpxImageClosure() { new ResolutionLayerComponentPositionIterator(context); break; default: - throw 'Unsupported progression order'; + throw 'Unsupported progression order ' + progressionOrder; } } function parseTilePackets(context, data, offset, dataLength) { @@ -1404,13 +902,6 @@ var JpxImage = (function JpxImageClosure() { } } } - // Table E.1 - var SubbandsGainLog2 = { - 'LL': 0, - 'LH': 1, - 'HL': 1, - 'HH': 2 - }; function transformTile(context, tile, c) { var component = tile.components[c]; var codingStyleParameters = component.codingStyleParameters; @@ -1552,303 +1043,820 @@ var JpxImage = (function JpxImageClosure() { tile.codingStyleDefaultParameters = context.currentTile.COD; } - function JpxImage() { - } - JpxImage.prototype = { - load: function jpxImageLoad(url) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'arraybuffer'; - xhr.onload = (function() { - // TODO catch parse error - var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); - this.parse(data); - if (this.onload) - this.onload(); - }).bind(this); - xhr.send(null); - }, - parse: function jpxImageParse(data) { - function ReadUint(data, offset, bytes) { - var n = 0; - for (var i = 0; i < bytes; i++) - n = n * 256 + (data[offset + i] & 0xFF); - return n; + // Section B.10.2 Tag trees + var TagTree = (function TagTreeClosure() { + function TagTree(width, height) { + var levelsLength = log2(Math.max(width, height)) + 1; + this.levels = []; + for (var i = 0; i < levelsLength; i++) { + var level = { + width: width, + height: height, + items: [] + }; + this.levels.push(level); + width = Math.ceil(width / 2); + height = Math.ceil(height / 2); } - var position = 0, length = data.length; - while (position < length) { - var headerSize = 8; - var lbox = ReadUint(data, position, 4); - var tbox = ReadUint(data, position + 4, 4); - position += headerSize; - if (lbox == 1) { - lbox = ReadUint(data, position, 8); - position += 8; - headerSize += 8; - } - if (lbox == 0) - lbox = length - position + headerSize; - if (lbox < headerSize) - throw 'Invalid box field size'; - var dataLength = lbox - headerSize; - var jumpDataLength = true; - switch (tbox) { - case 0x6A501A1A: // 'jP\032\032' - // TODO - break; - case 0x6A703268: // 'jp2h' - jumpDataLength = false; // parsing child boxes - break; - case 0x636F6C72: // 'colr' - // TODO - break; - case 0x6A703263: // 'jp2c' - this.parseCodestream(data, position, position + dataLength); - break; - } - if (jumpDataLength) - position += dataLength; - } - }, - parseCodestream: function jpxImageParseCodestream(data, start, end) { - var context = {}; - var position = start; - while (position < end) { - var code = readUint16(data, position); - position += 2; - - var length = 0, j; - switch (code) { - case 0xFF4F: // Start of codestream (SOC) - context.mainHeader = true; - break; - case 0xFFD9: // End of codestream (EOC) - break; - case 0xFF51: // Image and tile size (SIZ) - length = readUint16(data, position); - var siz = {}; - siz.Xsiz = readUint32(data, position + 4); - siz.Ysiz = readUint32(data, position + 8); - siz.XOsiz = readUint32(data, position + 12); - siz.YOsiz = readUint32(data, position + 16); - siz.XTsiz = readUint32(data, position + 20); - siz.YTsiz = readUint32(data, position + 24); - siz.XTOsiz = readUint32(data, position + 28); - siz.YTOsiz = readUint32(data, position + 32); - var componentsCount = readUint16(data, position + 36); - siz.Csiz = componentsCount; - var components = []; - j = position + 38; - for (var i = 0; i < componentsCount; i++) { - var component = { - precision: (data[j] & 0x7F) + 1, - isSigned: !!(data[j] & 0x80), - XRsiz: data[j + 1], - YRsiz: data[j + 1] - }; - calculateComponentDimensions(component, siz); - components.push(component); - } - context.SIZ = siz; - context.components = components; - calculateTileGrids(context, components); - context.QCC = []; - context.COC = []; - break; - case 0xFF5C: // Quantization default (QCD) - length = readUint16(data, position); - var qcd = {}; - j = position + 2; - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; - switch (sqcd & 0x1F) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - throw 'Invalid SQcd value'; - } - qcd.noQuantization = spqcdSize == 8; - qcd.scalarExpounded = scalarExpounded; - qcd.guardBits = sqcd >> 5; - var spqcds = []; - while (j < length + position) { - var spqcd = {}; - if (spqcdSize == 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; - } - spqcds.push(spqcd); - } - qcd.SPqcds = spqcds; - if (context.mainHeader) - context.QCD = qcd; - else { - context.currentTile.QCD = qcd; - context.currentTile.QCC = []; - } - break; - case 0xFF5D: // Quantization component (QCC) - length = readUint16(data, position); - var qcc = {}; - j = position + 2; - var cqcc; - if (context.SIZ.Csiz < 257) - cqcc = data[j++]; - else { - cqcc = readUint16(data, j); - j += 2; - } - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; - switch (sqcd & 0x1F) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - throw 'Invalid SQcd value'; - } - qcc.noQuantization = spqcdSize == 8; - qcc.scalarExpounded = scalarExpounded; - qcc.guardBits = sqcd >> 5; - var spqcds = []; - while (j < length + position) { - var spqcd = {}; - if (spqcdSize == 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; - } - spqcds.push(spqcd); - } - qcc.SPqcds = spqcds; - if (context.mainHeader) - context.QCC[cqcc] = qcc; - else - context.currentTile.QCC[cqcc] = qcc; - break; - case 0xFF52: // Coding style default (COD) - length = readUint16(data, position); - var cod = {}; - j = position + 2; - var scod = data[j++]; - cod.entropyCoderWithCustomPrecincts = !!(scod & 1); - cod.sopMarkerUsed = !!(scod & 2); - cod.ephMarkerUsed = !!(scod & 4); - var codingStyle = {}; - cod.progressionOrder = data[j++]; - cod.layersCount = readUint16(data, j); - j += 2; - cod.multipleComponentTransform = data[j++]; - - cod.decompositionLevelsCount = data[j++]; - cod.xcb = (data[j++] & 0xF) + 2; - cod.ycb = (data[j++] & 0xF) + 2; - var blockStyle = data[j++]; - cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); - cod.resetContextProbabilities = !!(blockStyle & 2); - cod.terminationOnEachCodingPass = !!(blockStyle & 4); - cod.verticalyStripe = !!(blockStyle & 8); - cod.predictableTermination = !!(blockStyle & 16); - cod.segmentationSymbolUsed = !!(blockStyle & 32); - cod.transformation = data[j++]; - if (cod.entropyCoderWithCustomPrecincts) { - var precinctsSizes = {}; - while (j < length + position) { - var precinctsSize = data[j]; - precinctsSizes.push({ - PPx: precinctsSize & 0xF, - PPy: precinctsSize >> 4 - }); - } - cod.precinctsSizes = precinctsSizes; - } - - if (cod.sopMarkerUsed || cod.ephMarkerUsed || - cod.selectiveArithmeticCodingBypass || - cod.resetContextProbabilities || - cod.terminationOnEachCodingPass || - cod.verticalyStripe || cod.predictableTermination || - cod.segmentationSymbolUsed) - throw 'Unsupported COD options: ' + uneval(cod); - - if (context.mainHeader) - context.COD = cod; - else { - context.currentTile.COD = cod; - context.currentTile.COC = []; - } - break; - case 0xFF90: // Start of tile-part (SOT) - length = readUint16(data, position); - var tile = {}; - tile.index = readUint16(data, position + 2); - tile.length = readUint32(data, position + 4); - tile.dataEnd = tile.length + position - 2; - tile.partIndex = data[position + 8]; - tile.partsCount = data[position + 9]; - - context.mainHeader = false; - if (tile.partIndex == 0) { - // reset component specific settings - tile.COD = context.COD; - tile.COC = context.COC.slice(0); // clone of the global COC - tile.QCD = context.QCD; - tile.QCC = context.QCC.slice(0); // clone of the global COC - } - context.currentTile = tile; - break; - case 0xFF93: // Start of data (SOD) - var tile = context.currentTile; - if (tile.partIndex == 0) { - initializeTile(context, tile.index); - buildPackets(context); - } - - // moving to the end of the data - length = tile.dataEnd - position; - - parseTilePackets(context, data, position, length); - break; - case 0xFF64: // Comment (COM) - length = readUint16(data, position); - // skipping content - break; - default: - throw 'Unknown codestream code: ' + code.toString(16); - } - position += length; - } - this.tiles = transformComponents(context); - this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; - this.height = context.SIZ.Ysiz - context.SIZ.YOsiz; - this.componentsCount = context.SIZ.Csiz; } - }; + TagTree.prototype = { + reset: function TagTree_reset(i, j) { + var currentLevel = 0, value = 0; + while (currentLevel < this.levels.length) { + var level = this.levels[currentLevel]; + var index = i + j * level.width; + if (index in level.items) { + value = level.items[index]; + break; + } + level.index = index; + i >>= 1; + j >>= 1; + currentLevel++; + } + currentLevel--; + var level = this.levels[currentLevel]; + level.items[level.index] = value; + this.currentLevel = currentLevel; + delete this.value; + }, + incrementValue: function TagTree_incrementValue() { + var level = this.levels[this.currentLevel]; + level.items[level.index]++; + }, + nextLevel: function TagTree_nextLevel() { + var currentLevel = this.currentLevel; + var level = this.levels[currentLevel]; + var value = level.items[level.index]; + currentLevel--; + if (currentLevel < 0) { + this.value = value; + return false; + } + + this.currentLevel = currentLevel; + var level = this.levels[currentLevel]; + level.items[level.index] = value; + return true; + } + }; + return TagTree; + })(); + + var InclusionTree = (function InclusionTreeClosure() { + function InclusionTree(width, height, defaultValue) { + var levelsLength = log2(Math.max(width, height)) + 1; + this.levels = []; + for (var i = 0; i < levelsLength; i++) { + var items = new Uint8Array(width * height); + for (var j = 0, jj = items.length; j < jj; j++) + items[j] = defaultValue; + + var level = { + width: width, + height: height, + items: items + }; + this.levels.push(level); + + width = Math.ceil(width / 2); + height = Math.ceil(height / 2); + } + } + InclusionTree.prototype = { + reset: function InclusionTree_reset(i, j, stopValue) { + var currentLevel = 0; + while (currentLevel < this.levels.length) { + var level = this.levels[currentLevel]; + var index = i + j * level.width; + level.index = index; + var value = level.items[index]; + + if (value == 0xFF) + break; + + if (value > stopValue) { + this.currentLevel = currentLevel; + // already know about this one, propagating the value to top levels + this.propagateValues(); + return false; + } + + i >>= 1; + j >>= 1; + currentLevel++; + } + this.currentLevel = currentLevel - 1; + return true; + }, + incrementValue: function InclusionTree_incrementValue(stopValue) { + var level = this.levels[this.currentLevel]; + level.items[level.index] = stopValue + 1; + this.propagateValues(); + }, + propagateValues: function InclusionTree_propagateValues() { + var levelIndex = this.currentLevel; + var level = this.levels[levelIndex]; + var currentValue = level.items[level.index]; + while (--levelIndex >= 0) { + var level = this.levels[levelIndex]; + level.items[level.index] = currentValue; + } + }, + nextLevel: function InclusionTree_nextLevel() { + var currentLevel = this.currentLevel; + var level = this.levels[currentLevel]; + var value = level.items[level.index]; + level.items[level.index] = 0xFF; + currentLevel--; + if (currentLevel < 0) + return false; + + this.currentLevel = currentLevel; + var level = this.levels[currentLevel]; + level.items[level.index] = value; + return true; + } + }; + return InclusionTree; + })(); + + // Implements C.3. Arithmetic decoding procedures + var ArithmeticDecoder = (function ArithmeticDecoderClosure() { + var QeTable = [ + {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1}, + {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0}, + {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0}, + {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0}, + {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0}, + {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0}, + {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1}, + {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0}, + {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0}, + {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0}, + {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0}, + {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0}, + {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0}, + {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0}, + {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1}, + {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0}, + {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0}, + {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0}, + {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0}, + {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0}, + {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0}, + {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0}, + {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0}, + {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0}, + {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0}, + {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0}, + {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0}, + {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0}, + {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0}, + {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0}, + {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0}, + {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0}, + {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0}, + {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0}, + {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0}, + {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0}, + {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0}, + {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0}, + {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0}, + {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0}, + {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0}, + {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0}, + {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0}, + {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0}, + {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0}, + {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0}, + {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0} + ]; + + function ArithmeticDecoder(data, start, end) { + this.data = data; + this.bp = start; + this.dataEnd = end; + + this.chigh = data[start]; + this.clow = 0; + + this.byteIn(); + + this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F); + this.clow = (this.clow << 7) & 0xFFFF; + this.ct -= 7; + this.a = 0x8000; + } + + ArithmeticDecoder.prototype = { + byteIn: function ArithmeticDecoder_byteIn() { + var data = this.data; + var bp = this.bp; + if (data[bp] == 0xFF) { + var b1 = data[bp + 1]; + if (b1 > 0x8F) { + this.clow += 0xFF00; + this.ct = 8; + } else { + bp++; + this.clow += (data[bp] << 9); + this.ct = 7; + this.bp = bp; + } + } else { + bp++; + this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00; + this.ct = 8; + this.bp = bp; + } + if (this.clow > 0xFFFF) { + this.chigh += (this.clow >> 16); + this.clow &= 0xFFFF; + } + }, + readBit: function ArithmeticDecoder_readBit(cx) { + var qeIcx = QeTable[cx.index].qe; + this.a -= qeIcx; + + if (this.chigh < qeIcx) { + var d = this.exchangeLps(cx); + this.renormD(); + return d; + } else { + this.chigh -= qeIcx; + if ((this.a & 0x8000) == 0) { + var d = this.exchangeMps(cx); + this.renormD(); + return d; + } else { + return cx.mps; + } + } + }, + renormD: function ArithmeticDecoder_renormD() { + do { + if (this.ct == 0) + this.byteIn(); + + this.a <<= 1; + this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); + this.clow = (this.clow << 1) & 0xFFFF; + this.ct--; + } while ((this.a & 0x8000) == 0); + }, + exchangeMps: function ArithmeticDecoder_exchangeMps(cx) { + var d; + var qeTableIcx = QeTable[cx.index]; + if (this.a < qeTableIcx.qe) { + d = 1 - cx.mps; + + if (qeTableIcx.switchFlag == 1) { + cx.mps = 1 - cx.mps; + } + cx.index = qeTableIcx.nlps; + } else { + d = cx.mps; + cx.index = qeTableIcx.nmps; + } + return d; + }, + exchangeLps: function ArithmeticDecoder_exchangeLps(cx) { + var d; + var qeTableIcx = QeTable[cx.index]; + if (this.a < qeTableIcx.qe) { + this.a = qeTableIcx.qe; + d = cx.mps; + cx.index = qeTableIcx.nmps; + } else { + this.a = qeTableIcx.qe; + d = 1 - cx.mps; + + if (qeTableIcx.switchFlag == 1) { + cx.mps = 1 - cx.mps; + } + cx.index = qeTableIcx.nlps; + } + return d; + } + }; + + return ArithmeticDecoder; + })(); + + // Section D. Coefficient bit modeling + var BitModel = (function BitModelClosure() { + // Table D-1 + // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), + // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) + var LLAndLHContextsLabel = new Uint8Array([ + 0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, + 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, + 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8 + ]); + var HLContextLabel = new Uint8Array([ + 0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, + 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, + 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8 + ]); + var HHContextLabel = new Uint8Array([ + 0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, + 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, + 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8 + ]); + + // Table D-2 + function calcSignContribution(significance0, sign0, significance1, sign1) { + if (significance1) { + if (!sign1) + return significance0 ? (!sign0 ? 1 : 0) : 1; + else + return significance0 ? (!sign0 ? 0 : -1) : -1; + } else + return significance0 ? (!sign0 ? 1 : -1) : 0; + } + // Table D-3 + var SignContextLabels = [ + {contextLabel: 13, xorBit: 0}, + {contextLabel: 12, xorBit: 0}, + {contextLabel: 11, xorBit: 0}, + {contextLabel: 10, xorBit: 0}, + {contextLabel: 9, xorBit: 0}, + {contextLabel: 10, xorBit: 1}, + {contextLabel: 11, xorBit: 1}, + {contextLabel: 12, xorBit: 1}, + {contextLabel: 13, xorBit: 1} + ]; + + function BitModel(width, height, subband, zeroBitPlanes) { + this.width = width; + this.height = height; + + this.contextLabelTable = subband == 'HH' ? HHContextLabel : + subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel; + + var coefficientCount = width * height; + + // coefficients outside the encoding region treated as insignificant + // add border state cells for significanceState + this.neighborsSignificance = new Uint8Array(coefficientCount); + this.coefficentsSign = new Uint8Array(coefficientCount); + this.coefficentsMagnitude = new Uint32Array(coefficientCount); + this.processingFlags = new Uint8Array(coefficientCount); + + var bitsDecoded = new Uint8Array(this.width * this.height); + for (var i = 0, ii = bitsDecoded.length; i < ii; i++) + bitsDecoded[i] = zeroBitPlanes; + this.bitsDecoded = bitsDecoded; + + this.reset(); + } + + BitModel.prototype = { + setDecoder: function BitModel_setDecoder(decoder) { + this.decoder = decoder; + }, + reset: function BitModel_reset() { + this.uniformContext = {index: 46, mps: 0}; + this.runLengthContext = {index: 3, mps: 0}; + this.contexts = []; + this.contexts.push({index: 4, mps: 0}); + for (var i = 1; i <= 16; i++) + this.contexts.push({index: 0, mps: 0}); + }, + setNeighborsSignificance: + function BitModel_setNeighborsSignificance(row, column) { + var neighborsSignificance = this.neighborsSignificance; + var width = this.width, height = this.height; + var index = row * width + column; + if (row > 0) { + if (column > 0) + neighborsSignificance[index - width - 1] += 0x10; + if (column + 1 < width) + neighborsSignificance[index - width + 1] += 0x10; + neighborsSignificance[index - width] += 0x04; + } + if (row + 1 < height) { + if (column > 0) + neighborsSignificance[index + width - 1] += 0x10; + if (column + 1 < width) + neighborsSignificance[index + width + 1] += 0x10; + neighborsSignificance[index + width] += 0x04; + } + if (column > 0) + neighborsSignificance[index - 1] += 0x01; + if (column + 1 < width) + neighborsSignificance[index + 1] += 0x01; + neighborsSignificance[index] |= 0x80; + }, + runSignificancePropogationPass: + function BitModel_runSignificancePropogationPass() { + var decoder = this.decoder; + var width = this.width, height = this.height; + var coefficentsMagnitude = this.coefficentsMagnitude; + var coefficentsSign = this.coefficentsSign; + var contextLabels = this.contextLabels; + var neighborsSignificance = this.neighborsSignificance; + var processingFlags = this.processingFlags; + var contexts = this.contexts; + var labels = this.contextLabelTable; + var bitsDecoded = this.bitsDecoded; + // clear processed flag + var processedInverseMask = ~1; + var processedMask = 1; + var firstMagnitudeBitMask = 2; + for (var q = 0, qq = width * height; q < qq; q++) + processingFlags[q] &= processedInverseMask; + + for (var i0 = 0; i0 < height; i0 += 4) { + for (var j = 0; j < width; j++) { + var index = i0 * width + j; + for (var i1 = 0; i1 < 4; i1++, index += width) { + var i = i0 + i1; + if (i >= height) + break; + + if (coefficentsMagnitude[index] || !neighborsSignificance[index]) + continue; + + var contextLabel = labels[neighborsSignificance[index]]; + var cx = contexts[contextLabel]; + var decision = decoder.readBit(cx); + if (decision) { + var sign = this.decodeSignBit(i, j); + coefficentsSign[index] = sign; + coefficentsMagnitude[index] = 1; + this.setNeighborsSignificance(i, j); + processingFlags[index] |= firstMagnitudeBitMask; + } + bitsDecoded[index]++; + processingFlags[index] |= processedMask; + } + } + } + }, + decodeSignBit: function BitModel_decodeSignBit(row, column) { + var width = this.width, height = this.height; + var index = row * width + column; + var coefficentsMagnitude = this.coefficentsMagnitude; + var coefficentsSign = this.coefficentsSign; + var horizontalContribution = calcSignContribution( + column > 0 && coefficentsMagnitude[index - 1], + coefficentsSign[index - 1], + column + 1 < width && coefficentsMagnitude[index + 1], + coefficentsSign[index + 1]); + var verticalContribution = calcSignContribution( + row > 0 && coefficentsMagnitude[index - width], + coefficentsSign[index - width], + row + 1 < height && coefficentsMagnitude[index + width], + coefficentsSign[index + width]); + + var contextLabelAndXor = SignContextLabels[ + 3 * (1 - horizontalContribution) + (1 - verticalContribution)]; + var contextLabel = contextLabelAndXor.contextLabel; + var cx = this.contexts[contextLabel]; + var decoded = this.decoder.readBit(cx); + return decoded ^ contextLabelAndXor.xorBit; + }, + runMagnitudeRefinementPass: + function BitModel_runMagnitudeRefinementPass() { + var decoder = this.decoder; + var width = this.width, height = this.height; + var coefficentsMagnitude = this.coefficentsMagnitude; + var neighborsSignificance = this.neighborsSignificance; + var contexts = this.contexts; + var bitsDecoded = this.bitsDecoded; + var processingFlags = this.processingFlags; + var processedMask = 1; + var firstMagnitudeBitMask = 2; + for (var i0 = 0; i0 < height; i0 += 4) { + for (var j = 0; j < width; j++) { + for (var i1 = 0; i1 < 4; i1++) { + var i = i0 + i1; + if (i >= height) + break; + var index = i * width + j; + + // significant but not those that have just become + if (!coefficentsMagnitude[index] || + (processingFlags[index] & processedMask) != 0) + continue; + + var contextLabel = 16; + if ((processingFlags[index] & + firstMagnitudeBitMask) != 0) { + processingFlags[i * width + j] ^= firstMagnitudeBitMask; + // first refinement + var significance = neighborsSignificance[index]; + var sumOfSignificance = (significance & 3) + + ((significance >> 2) & 3) + ((significance >> 4) & 7); + contextLabel = sumOfSignificance >= 1 ? 15 : 14; + } + + var cx = contexts[contextLabel]; + var bit = decoder.readBit(cx); + coefficentsMagnitude[index] = + (coefficentsMagnitude[index] << 1) | bit; + bitsDecoded[index]++; + processingFlags[index] |= processedMask; + } + } + } + }, + runCleanupPass: function BitModel_runCleanupPass() { + var decoder = this.decoder; + var width = this.width, height = this.height; + var neighborsSignificance = this.neighborsSignificance; + var significanceState = this.significanceState; + var coefficentsMagnitude = this.coefficentsMagnitude; + var coefficentsSign = this.coefficentsSign; + var contexts = this.contexts; + var labels = this.contextLabelTable; + var bitsDecoded = this.bitsDecoded; + var processingFlags = this.processingFlags; + var processedMask = 1; + var firstMagnitudeBitMask = 2; + var oneRowDown = width; + var twoRowsDown = width * 2; + var threeRowsDown = width * 3; + for (var i0 = 0; i0 < height; i0 += 4) { + for (var j = 0; j < width; j++) { + var index0 = i0 * width + j; + // using the property: labels[neighborsSignificance[index]] == 0 + // when neighborsSignificance[index] == 0 + var allEmpty = i0 + 3 < height && + processingFlags[index0] == 0 && + processingFlags[index0 + oneRowDown] == 0 && + processingFlags[index0 + twoRowsDown] == 0 && + processingFlags[index0 + threeRowsDown] == 0 && + neighborsSignificance[index0] == 0 && + neighborsSignificance[index0 + oneRowDown] == 0 && + neighborsSignificance[index0 + twoRowsDown] == 0 && + neighborsSignificance[index0 + threeRowsDown] == 0; + var i1 = 0, index = index0; + var cx, i; + if (allEmpty) { + cx = this.runLengthContext; + var hasSignificantCoefficent = decoder.readBit(cx); + if (!hasSignificantCoefficent) { + bitsDecoded[index0]++; + bitsDecoded[index0 + oneRowDown]++; + bitsDecoded[index0 + twoRowsDown]++; + bitsDecoded[index0 + threeRowsDown]++; + continue; // next column + } + cx = this.uniformContext; + i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx); + i = i0 + i1; + index += i1 * width; + + var sign = this.decodeSignBit(i, j); + coefficentsSign[index] = sign; + coefficentsMagnitude[index] = 1; + this.setNeighborsSignificance(i, j); + processingFlags[index] |= firstMagnitudeBitMask; + + index = index0; + for (var i2 = i0; i2 <= i; i2++, index += width) + bitsDecoded[index]++; + + i1++; + } + for (; i1 < 4; i1++, index += width) { + i = i0 + i1; + if (i >= height) + break; + + if (coefficentsMagnitude[index] || + (processingFlags[index] & processedMask) != 0) + continue; + + var contextLabel = labels[neighborsSignificance[index]]; + cx = contexts[contextLabel]; + var decision = decoder.readBit(cx); + if (decision == 1) { + var sign = this.decodeSignBit(i, j); + coefficentsSign[index] = sign; + coefficentsMagnitude[index] = 1; + this.setNeighborsSignificance(i, j); + processingFlags[index] |= firstMagnitudeBitMask; + } + bitsDecoded[index]++; + } + } + } + } + }; + + return BitModel; + })(); + + // Section F, Discrete wavelet transofrmation + var Transform = (function TransformClosure() { + function Transform() { + } + Transform.prototype.calculate = + function transformCalculate(subbands, u0, v0) { + var ll = subbands[0]; + for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) { + ll = this.iterate(ll, subbands[i], subbands[i + 1], + subbands[i + 2], u0, v0); + } + return ll; + }; + Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh, + u0, v0) { + var llWidth = ll.width, llHeight = ll.height, llItems = ll.items; + var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items; + var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items; + var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items; + + // Section F.3.3 interleave + var width = llWidth + hlWidth; + var height = llHeight + lhHeight; + var items = new Float32Array(width * height); + for (var i = 0, ii = llHeight; i < ii; i++) { + var k = i * llWidth, l = i * 2 * width; + for (var j = 0, jj = llWidth; j < jj; j++, k++, l += 2) + items[l] = llItems[k]; + } + for (var i = 0, ii = hlHeight; i < ii; i++) { + var k = i * hlWidth, l = i * 2 * width + 1; + for (var j = 0, jj = hlWidth; j < jj; j++, k++, l += 2) + items[l] = hlItems[k]; + } + for (var i = 0, ii = lhHeight; i < ii; i++) { + var k = i * lhWidth, l = (i * 2 + 1) * width; + for (var j = 0, jj = lhWidth; j < jj; j++, k++, l += 2) + items[l] = lhItems[k]; + } + for (var i = 0, ii = hhHeight; i < ii; i++) { + var k = i * hhWidth, l = (i * 2 + 1) * width + 1; + for (var j = 0, jj = hhWidth; j < jj; j++, k++, l += 2) + items[l] = hhItems[k]; + } + + var bufferPadding = 4; + var bufferLength = new Float32Array(Math.max(width, height) + + 2 * bufferPadding); + var buffer = new Float32Array(bufferLength); + var bufferOut = new Float32Array(bufferLength); + + // Section F.3.4 HOR_SR + for (var v = 0; v < height; v++) { + if (width == 1) { + // if width = 1, when u0 even keep items as is, when odd divide by 2 + if ((u0 % 1) != 0) { + items[v * width] /= 2; + } + continue; + } + + var k = v * width; + var l = bufferPadding; + for (var u = 0; u < width; u++, k++, l++) + buffer[l] = items[k]; + + // Section F.3.7 extending... using max extension of 4 + var i1 = bufferPadding - 1, j1 = bufferPadding + 1; + var i2 = bufferPadding + width - 2, j2 = bufferPadding + width; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + + this.filter(buffer, bufferPadding, width, u0, bufferOut); + + k = v * width; + l = bufferPadding; + for (var u = 0; u < width; u++, k++, l++) + items[k] = bufferOut[l]; + } + + // Section F.3.5 VER_SR + for (var u = 0; u < width; u++) { + if (height == 1) { + // if height = 1, when v0 even keep items as is, when odd divide by 2 + if ((v0 % 1) != 0) { + items[u] /= 2; + } + continue; + } + + var k = u; + var l = bufferPadding; + for (var v = 0; v < height; v++, k += width, l++) + buffer[l] = items[k]; + + // Section F.3.7 extending... using max extension of 4 + var i1 = bufferPadding - 1, j1 = bufferPadding + 1; + var i2 = bufferPadding + height - 2, j2 = bufferPadding + height; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + + this.filter(buffer, bufferPadding, height, v0, bufferOut); + + k = u; + l = bufferPadding; + for (var v = 0; v < height; v++, k += width, l++) + items[k] = bufferOut[l]; + } + return { + width: width, + height: height, + items: items + }; + }; + return Transform; + })(); + + // Section 3.8.2 Irreversible 9-7 filter + var IrreversibleTransform = (function IrreversibleTransformClosure() { + function IrreversibleTransform() { + Transform.call(this); + } + + IrreversibleTransform.prototype = Object.create(Transform.prototype); + IrreversibleTransform.prototype.filter = + function irreversibleTransformFilter(y, offset, length, i0, x) { + var i0_ = Math.floor(i0 / 2); + var i1_ = Math.floor((i0 + length) / 2); + var offset_ = offset - (i0 % 1); + + var alpha = -1.586134342059924; + var beta = -0.052980118572961; + var gamma = 0.882911075530934; + var delta = 0.443506852043971; + var K = 1.230174104914001; + var K_ = 1 / K; + + // step 1 + var j = offset_ - 2; + for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) + x[j] = K * y[j]; + + // step 2 + var j = offset_ - 3; + for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2) + x[j] = K_ * y[j]; + + // step 3 + var j = offset_ - 2; + for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2) + x[j] -= delta * (x[j - 1] + x[j + 1]); + + // step 4 + var j = offset_ - 1; + for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2) + x[j] -= gamma * (x[j - 1] + x[j + 1]); + + // step 5 + var j = offset_; + for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2) + x[j] -= beta * (x[j - 1] + x[j + 1]); + + // step 6 + var j = offset_ + 1; + for (var n = i0_, nn = i1_; n < nn; n++, j += 2) + x[j] -= alpha * (x[j - 1] + x[j + 1]); + }; + + return IrreversibleTransform; + })(); + + // Section 3.8.1 Reversible 5-3 filter + var ReversibleTransform = (function ReversibleTransformClosure() { + function ReversibleTransform() { + Transform.call(this); + } + + ReversibleTransform.prototype = Object.create(Transform.prototype); + ReversibleTransform.prototype.filter = + function reversibleTransformFilter(y, offset, length, i0, x) { + var i0_ = Math.floor(i0 / 2); + var i1_ = Math.floor((i0 + length) / 2); + var offset_ = offset - (i0 % 1); + + for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2) + x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4); + + for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2) + x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2); + }; + + return ReversibleTransform; + })(); + return JpxImage; })(); + diff --git a/src/metadata.js b/src/metadata.js new file mode 100644 index 000000000..f38056168 --- /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 Metadata_parse() { + 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 Metadata_get(name) { + return this.metadata[name] || null; + }, + + has: function Metadata_has(name) { + return typeof this.metadata[name] !== 'undefined'; + } + }; + + return Metadata; +})(); diff --git a/src/obj.js b/src/obj.js index ef7932546..200b40a7f 100644 --- a/src/obj.js +++ b/src/obj.js @@ -22,7 +22,7 @@ var Cmd = (function CmdClosure() { var cmdCache = {}; - Cmd.get = function cmdGet(cmd) { + Cmd.get = function Cmd_get(cmd) { var cmdValue = cmdCache[cmd]; if (cmdValue) return cmdValue; @@ -34,36 +34,52 @@ var Cmd = (function CmdClosure() { })(); var Dict = (function DictClosure() { - function Dict() { + // xref is optional + function Dict(xref) { + // Map should only be used internally, use functions below to access. this.map = Object.create(null); + this.xref = xref; } Dict.prototype = { - get: function dictGet(key1, key2, key3) { + // automatically dereferences Ref objects + get: function Dict_get(key1, key2, key3) { var value; + var xref = this.xref; if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || typeof key2 == 'undefined') { - return value; + return xref ? this.xref.fetchIfRef(value) : value; } if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || typeof key3 == 'undefined') { - return value; + return xref ? this.xref.fetchIfRef(value) : value; } - - return this.map[key3] || null; + value = this.map[key3] || null; + return xref ? this.xref.fetchIfRef(value) : value; + }, + // no dereferencing + getRaw: function Dict_getRaw(key) { + return this.map[key]; + }, + // creates new map and dereferences all Refs + getAll: function Dict_getAll() { + var all = {}; + for (var key in this.map) + all[key] = this.get(key); + return all; }, - set: function dictSet(key, value) { + set: function Dict_set(key, value) { this.map[key] = value; }, - has: function dictHas(key) { + has: function Dict_has(key) { return key in this.map; }, - forEach: function dictForEach(callback) { + forEach: function Dict_forEach(callback) { for (var key in this.map) { - callback(key, this.map[key]); + callback(key, this.get(key)); } } }; @@ -90,11 +106,11 @@ var RefSet = (function RefSetClosure() { } RefSet.prototype = { - has: function refSetHas(ref) { + has: function RefSet_has(ref) { return !!this.dict['R' + ref.num + '.' + ref.gen]; }, - put: function refSetPut(ref) { + put: function RefSet_put(ref) { this.dict['R' + ref.num + '.' + ref.gen] = ref; } }; @@ -111,20 +127,33 @@ var Catalog = (function CatalogClosure() { } Catalog.prototype = { + get metadata() { + var stream = this.catDict.get('Metadata'); + 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'); - var xrefObj = this.xref.fetch(pagesObj); - assertWellFormed(isDict(xrefObj), 'invalid top-level pages dictionary'); + assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary'); // shadow the prototype getter - return shadow(this, 'toplevelPagesDict', xrefObj); + return shadow(this, 'toplevelPagesDict', pagesObj); }, get documentOutline() { var xref = this.xref; - var obj = xref.fetchIfRef(this.catDict.get('Outlines')); + var obj = this.catDict.get('Outlines'); var root = { items: [] }; if (isDict(obj)) { - obj = obj.get('First'); + obj = obj.getRaw('First'); var processed = new RefSet(); if (isRef(obj)) { var queue = [{obj: obj, parent: root}]; @@ -133,18 +162,20 @@ var Catalog = (function CatalogClosure() { processed.put(obj); while (queue.length > 0) { var i = queue.shift(); - var outlineDict = xref.fetch(i.obj); + var outlineDict = xref.fetchIfRef(i.obj); + if (outlineDict === null) + continue; if (!outlineDict.has('Title')) error('Invalid outline item'); var dest = outlineDict.get('A'); if (dest) - dest = xref.fetchIfRef(dest).get('D'); + dest = dest.get('D'); else if (outlineDict.has('Dest')) { - dest = outlineDict.get('Dest'); + dest = outlineDict.getRaw('Dest'); if (isName(dest)) dest = dest.name; } - var title = xref.fetchIfRef(outlineDict.get('Title')); + var title = outlineDict.get('Title'); var outlineItem = { dest: dest, title: stringToPDFString(title), @@ -155,12 +186,12 @@ var Catalog = (function CatalogClosure() { items: [] }; i.parent.items.push(outlineItem); - obj = outlineDict.get('First'); + obj = outlineDict.getRaw('First'); if (isRef(obj) && !processed.has(obj)) { queue.push({obj: obj, parent: outlineItem}); processed.put(obj); } - obj = outlineDict.get('Next'); + obj = outlineDict.getRaw('Next'); if (isRef(obj) && !processed.has(obj)) { queue.push({obj: obj, parent: i.parent}); processed.put(obj); @@ -180,7 +211,7 @@ var Catalog = (function CatalogClosure() { // shadow the prototype getter return shadow(this, 'num', obj); }, - traverseKids: function catalogTraverseKids(pagesDict) { + traverseKids: function Catalog_traverseKids(pagesDict) { var pageCache = this.pageCache; var kids = pagesDict.get('Kids'); assertWellFormed(isArray(kids), @@ -188,7 +219,7 @@ var Catalog = (function CatalogClosure() { for (var i = 0, ii = kids.length; i < ii; ++i) { var kid = kids[i]; assertWellFormed(isRef(kid), - 'page dictionary kid is not a reference'); + 'page dictionary kid is not a reference'); var obj = this.xref.fetch(kid); if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) { pageCache.push(new Page(this.xref, pageCache.length, obj, kid)); @@ -202,8 +233,7 @@ var Catalog = (function CatalogClosure() { } }, get destinations() { - function fetchDestination(xref, ref) { - var dest = xref.fetchIfRef(ref); + function fetchDestination(dest) { return isDict(dest) ? dest.get('D') : dest; } @@ -211,16 +241,16 @@ var Catalog = (function CatalogClosure() { var dests = {}, nameTreeRef, nameDictionaryRef; var obj = this.catDict.get('Names'); if (obj) - nameTreeRef = xref.fetchIfRef(obj).get('Dests'); + nameTreeRef = obj.getRaw('Dests'); else if (this.catDict.has('Dests')) nameDictionaryRef = this.catDict.get('Dests'); if (nameDictionaryRef) { // reading simple destination dictionary - obj = xref.fetchIfRef(nameDictionaryRef); + obj = nameDictionaryRef; obj.forEach(function catalogForEach(key, value) { if (!value) return; - dests[key] = fetchDestination(xref, value); + dests[key] = fetchDestination(value); }); } if (nameTreeRef) { @@ -244,13 +274,13 @@ var Catalog = (function CatalogClosure() { } var names = obj.get('Names'); for (i = 0, n = names.length; i < n; i += 2) { - dests[names[i]] = fetchDestination(xref, names[i + 1]); + dests[names[i]] = fetchDestination(xref.fetchIfRef(names[i + 1])); } } } return shadow(this, 'destinations', dests); }, - getPage: function catalogGetPage(n) { + getPage: function Catalog_getPage(n) { var pageCache = this.pageCache; if (!pageCache) { pageCache = this.pageCache = []; @@ -269,6 +299,7 @@ var XRef = (function XRefClosure() { this.entries = []; this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); + trailerDict.xref = this; this.trailer = trailerDict; // prepare the XRef cache this.cache = []; @@ -276,89 +307,84 @@ var XRef = (function XRefClosure() { var encrypt = trailerDict.get('Encrypt'); if (encrypt) { var fileId = trailerDict.get('ID'); - this.encrypt = new CipherTransformFactory(this.fetch(encrypt), + this.encrypt = new CipherTransformFactory(encrypt, fileId[0] /*, password */); } // get the root dictionary (catalog) object - if (!isRef(this.root = trailerDict.get('Root'))) + if (!(this.root = trailerDict.get('Root'))) error('Invalid root reference'); } XRef.prototype = { - readXRefTable: function readXRefTable(parser) { + readXRefTable: function XRef_readXRefTable(parser) { + // Example of cross-reference table: + // xref + // 0 1 <-- subsection header (first obj #, obj count) + // 0000000000 65535 f <-- actual object (offset, generation #, f/n) + // 23 2 <-- subsection header ... and so on ... + // 0000025518 00002 n + // 0000025635 00000 n + // trailer + // ... + + // Outer loop is over subsection headers var obj; - while (true) { - if (isCmd(obj = parser.getObj(), 'trailer')) - break; - if (!isInt(obj)) - error('Invalid XRef table'); - var first = obj; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table'); - var n = obj; - if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) - error('Invalid XRef table: ' + first + ', ' + n); - for (var i = first; i < first + n; ++i) { + while (!isCmd(obj = parser.getObj(), 'trailer')) { + var first = obj, + count = parser.getObj(); + + if (!isInt(first) || !isInt(count)) + error('Invalid XRef table: wrong types in subsection header'); + + // Inner loop is over objects themselves + for (var i = 0; i < count; i++) { var entry = {}; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - entry.offset = obj; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - entry.gen = obj; - obj = parser.getObj(); - if (isCmd(obj, 'n')) { - entry.uncompressed = true; - } else if (isCmd(obj, 'f')) { + entry.offset = parser.getObj(); + entry.gen = parser.getObj(); + var type = parser.getObj(); + + if (isCmd(type, 'f')) entry.free = true; - } else { - error('Invalid XRef table: ' + first + ', ' + n); - } - if (!this.entries[i]) { - // In some buggy PDF files the xref table claims to start at 1 - // instead of 0. - if (i == 1 && first == 1 && - entry.offset == 0 && entry.gen == 65535 && entry.free) { - i = first = 0; - } - this.entries[i] = entry; + else if (isCmd(type, 'n')) + entry.uncompressed = true; + + // Validate entry obj + if (!isInt(entry.offset) || !isInt(entry.gen) || + !(entry.free || entry.uncompressed)) { + error('Invalid entry in XRef subsection: ' + first + ', ' + count); } + + if (!this.entries[i + first]) + this.entries[i + first] = entry; } } - // read the trailer dictionary - var dict; - if (!isDict(dict = parser.getObj())) - error('Invalid XRef table'); + // Sanity check: as per spec, first object must have these properties + if (this.entries[0] && + !(this.entries[0].gen === 65535 && this.entries[0].free)) + error('Invalid XRef table: unexpected first object'); - // get the 'Prev' pointer - var prev; - obj = dict.get('Prev'); - if (isInt(obj)) { - prev = obj; - } else if (isRef(obj)) { - // certain buggy PDF generators generate "/Prev NNN 0 R" instead - // of "/Prev NNN" - prev = obj.num; - } - if (prev) { - this.readXRef(prev); - } + // Sanity check + if (!isCmd(obj, 'trailer')) + error('Invalid XRef table: could not find trailer dictionary'); - // check for 'XRefStm' key - if (isInt(obj = dict.get('XRefStm'))) { - var pos = obj; - // ignore previously loaded xref streams (possible infinite recursion) - if (!(pos in this.xrefstms)) { - this.xrefstms[pos] = 1; - this.readXRef(pos); - } - } + // Read trailer dictionary, e.g. + // trailer + // << /Size 22 + // /Root 20R + // /Info 10R + // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ] + // >> + // The parser goes through the entire stream << ... >> and provides + // a getter interface for the key-value table + var dict = parser.getObj(); + if (!isDict(dict)) + error('Invalid XRef table: could not parse trailer dictionary'); return dict; }, - readXRefStream: function readXRefStream(stream) { + readXRefStream: function XRef_readXRefStream(stream) { var streamParameters = stream.parameters; var byteWidths = streamParameters.get('W'); var range = streamParameters.get('Index'); @@ -407,12 +433,9 @@ var XRef = (function XRefClosure() { } range.splice(0, 2); } - var prev = streamParameters.get('Prev'); - if (isInt(prev)) - this.readXRef(prev); return streamParameters; }, - indexObjects: function indexObjects() { + indexObjects: function XRef_indexObjects() { // Simple scan through the PDF content to find objects, // trailers and XRef streams. function readToken(data, offset) { @@ -504,7 +527,7 @@ var XRef = (function XRefClosure() { var dict; for (var i = 0, ii = trailers.length; i < ii; ++i) { stream.pos = trailers[i]; - var parser = new Parser(new Lexer(stream), true); + var parser = new Parser(new Lexer(stream), true, null); var obj = parser.getObj(); if (!isCmd(obj, 'trailer')) continue; @@ -520,46 +543,73 @@ var XRef = (function XRefClosure() { return dict; // nothing helps error('Invalid PDF structure'); - return null; }, - readXRef: function readXref(startXRef) { + readXRef: function XRef_readXRef(startXRef) { var stream = this.stream; stream.pos = startXRef; try { - var parser = new Parser(new Lexer(stream), true); + var parser = new Parser(new Lexer(stream), true, null); var obj = parser.getObj(); + var dict; - // parse an old-style xref table - if (isCmd(obj, 'xref')) - return this.readXRefTable(parser); + // Get dictionary + if (isCmd(obj, 'xref')) { + // Parse end-of-file XRef + dict = this.readXRefTable(parser); - // parse an xref stream - if (isInt(obj)) { + // Recursively get other XRefs 'XRefStm', if any + obj = dict.get('XRefStm'); + if (isInt(obj)) { + var pos = obj; + // ignore previously loaded xref streams + // (possible infinite recursion) + if (!(pos in this.xrefstms)) { + this.xrefstms[pos] = 1; + this.readXRef(pos); + } + } + } else if (isInt(obj)) { + // Parse in-stream XRef if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || !isStream(obj = parser.getObj())) { error('Invalid XRef stream'); } - return this.readXRefStream(obj); + dict = this.readXRefStream(obj); } + + // Recursively get previous dictionary, if any + obj = dict.get('Prev'); + if (isInt(obj)) + this.readXRef(obj); + else if (isRef(obj)) { + // The spec says Prev must not be a reference, i.e. "/Prev NNN" + // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R" + this.readXRef(obj.num); + } + + return dict; } catch (e) { - log('Reading of the xref table/stream failed: ' + e); + log('(while reading XRef): ' + e); } warn('Indexing all PDF objects'); return this.indexObjects(); }, - getEntry: function xRefGetEntry(i) { + getEntry: function XRef_getEntry(i) { var e = this.entries[i]; + if (e === null) + return null; return e.free ? null : e; // returns null is the entry is free }, - fetchIfRef: function xRefFetchIfRef(obj) { + fetchIfRef: function XRef_fetchIfRef(obj) { if (!isRef(obj)) return obj; return this.fetch(obj); }, - fetch: function xRefFetch(ref, suppressEncryption) { + fetch: function XRef_fetch(ref, suppressEncryption) { + assertWellFormed(isRef(ref), 'ref object is not a reference'); var num = ref.num; if (num in this.cache) return this.cache[num]; @@ -574,7 +624,7 @@ var XRef = (function XRefClosure() { var stream, parser; if (e.uncompressed) { if (e.gen != gen) - throw ('inconsistent generation in XRef'); + error('inconsistent generation in XRef'); stream = this.stream.makeSubStream(e.offset); parser = new Parser(new Lexer(stream), true, this); var obj1 = parser.getObj(); @@ -621,7 +671,7 @@ var XRef = (function XRefClosure() { if (!isInt(first) || !isInt(n)) { error('invalid first and n parameters for ObjStm stream'); } - parser = new Parser(new Lexer(stream), false); + parser = new Parser(new Lexer(stream), false, this); var i, entries = [], nums = []; // read the object numbers to populate cache for (i = 0; i < n; ++i) { @@ -646,8 +696,8 @@ var XRef = (function XRefClosure() { } return e; }, - getCatalogObj: function xRefGetCatalogObj() { - return this.fetch(this.root); + getCatalogObj: function XRef_getCatalogObj() { + return this.root; } }; @@ -673,7 +723,7 @@ var PDFObjects = (function PDFObjectsClosure() { * Ensures there is an object defined for `objId`. Stores `data` on the * object *if* it is created. */ - ensureObj: function pdfObjectsEnsureObj(objId, data) { + ensureObj: function PDFObjects_ensureObj(objId, data) { if (this.objs[objId]) return this.objs[objId]; return this.objs[objId] = new Promise(objId, data); @@ -688,7 +738,7 @@ var PDFObjects = (function PDFObjectsClosure() { * function and the object is already resolved, the callback gets called * right away. */ - get: function pdfObjectsGet(objId, callback) { + get: function PDFObjects_get(objId, callback) { // If there is a callback, then the get can be async and the object is // not required to be resolved right now if (callback) { @@ -702,18 +752,16 @@ var PDFObjects = (function PDFObjectsClosure() { // If there isn't an object yet or the object isn't resolved, then the // data isn't ready yet! - if (!obj || !obj.isResolved) { - throw 'Requesting object that isn\'t resolved yet ' + objId; - return null; - } else { - return obj.data; - } + if (!obj || !obj.isResolved) + error('Requesting object that isn\'t resolved yet ' + objId); + + return obj.data; }, /** * Resolves the object `objId` with optional `data`. */ - resolve: function pdfObjectsResolve(objId, data) { + resolve: function PDFObjects_resolve(objId, data) { var objs = this.objs; // In case there is a promise already on this object, just resolve it. @@ -724,11 +772,11 @@ var PDFObjects = (function PDFObjectsClosure() { } }, - onData: function pdfObjectsOnData(objId, callback) { + onData: function PDFObjects_onData(objId, callback) { this.ensureObj(objId).onData(callback); }, - isResolved: function pdfObjectsIsResolved(objId) { + isResolved: function PDFObjects_isResolved(objId) { var objs = this.objs; if (!objs[objId]) { return false; @@ -737,7 +785,7 @@ var PDFObjects = (function PDFObjectsClosure() { } }, - hasData: function pdfObjectsHasData(objId) { + hasData: function PDFObjects_hasData(objId) { var objs = this.objs; if (!objs[objId]) { return false; @@ -749,7 +797,7 @@ var PDFObjects = (function PDFObjectsClosure() { /** * Sets the data of an object but *doesn't* resolve it. */ - setData: function pdfObjectsSetData(objId, data) { + setData: function PDFObjects_setData(objId, data) { // Watchout! If you call `this.ensureObj(objId, data)` you're going to // create a *resolved* promise which shouldn't be the case! this.ensureObj(objId).data = data; diff --git a/src/parser.js b/src/parser.js index 4a884e0d4..1c50d0f5f 100644 --- a/src/parser.js +++ b/src/parser.js @@ -19,11 +19,11 @@ var Parser = (function ParserClosure() { } Parser.prototype = { - refill: function parserRefill() { + refill: function Parser_refill() { this.buf1 = this.lexer.getObj(); this.buf2 = this.lexer.getObj(); }, - shift: function parserShift() { + shift: function Parser_shift() { if (isCmd(this.buf2, 'ID')) { this.buf1 = this.buf2; this.buf2 = null; @@ -34,7 +34,7 @@ var Parser = (function ParserClosure() { this.buf2 = this.lexer.getObj(); } }, - getObj: function parserGetObj(cipherTransform) { + getObj: function Parser_getObj(cipherTransform) { if (isCmd(this.buf1, 'BI')) { // inline image this.shift(); return this.makeInlineImage(cipherTransform); @@ -51,17 +51,16 @@ var Parser = (function ParserClosure() { } if (isCmd(this.buf1, '<<')) { // dictionary or stream this.shift(); - var dict = new Dict(); + var dict = new Dict(this.xref); while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { + if (!isName(this.buf1)) error('Dictionary key must be a name object'); - } else { - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } + + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); } if (isEOF(this.buf1)) error('End of file inside dictionary'); @@ -99,38 +98,37 @@ var Parser = (function ParserClosure() { this.shift(); return obj; }, - makeInlineImage: function parserMakeInlineImage(cipherTransform) { + makeInlineImage: function Parser_makeInlineImage(cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; // parse dictionary var dict = new Dict(); while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { + if (!isName(this.buf1)) error('Dictionary key must be a name object'); - } else { - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } + + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); } // parse image stream var startPos = stream.pos; - // searching for the /\sEI\s/ + // searching for the /EI\s/ var state = 0, ch; while (state != 4 && (ch = stream.getByte()) != null) { switch (ch) { case 0x20: case 0x0D: case 0x0A: - state = state === 3 ? 4 : 1; + state = state === 3 ? 4 : 0; break; case 0x45: - state = state === 1 ? 2 : 0; + state = 2; break; case 0x49: state = state === 2 ? 3 : 0; @@ -162,7 +160,11 @@ var Parser = (function ParserClosure() { return imageStream; }, - makeStream: function parserMakeStream(dict, cipherTransform) { + fetchIfRef: function Parser_fetchIfRef(obj) { + // not relying on the xref.fetchIfRef -- xref might not be set + return isRef(obj) ? this.xref.fetch(obj) : obj; + }, + makeStream: function Parser_makeStream(dict, cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; @@ -171,14 +173,9 @@ var Parser = (function ParserClosure() { var pos = stream.pos; // get length - var length = dict.get('Length'); - var xref = this.xref; - if (xref) - length = xref.fetchIfRef(length); - if (!isInt(length)) { + var length = this.fetchIfRef(dict.get('Length')); + if (!isInt(length)) error('Bad ' + length + ' attribute in stream'); - length = 0; - } // skip over the stream data stream.pos = pos + length; @@ -195,9 +192,9 @@ var Parser = (function ParserClosure() { stream.parameters = dict; return stream; }, - filter: function parserFilter(stream, dict, length) { - var filter = dict.get('Filter', 'F'); - var params = dict.get('DecodeParms', 'DP'); + filter: function Parser_filter(stream, dict, length) { + var filter = this.fetchIfRef(dict.get('Filter', 'F')); + var params = this.fetchIfRef(dict.get('DecodeParms', 'DP')); if (isName(filter)) return this.makeFilter(stream, filter.name, length, params); if (isArray(filter)) { @@ -207,19 +204,18 @@ var Parser = (function ParserClosure() { filter = filterArray[i]; if (!isName(filter)) error('Bad filter name: ' + filter); - else { - params = null; - if (isArray(paramsArray) && (i in paramsArray)) - params = paramsArray[i]; - stream = this.makeFilter(stream, filter.name, length, params); - // after the first stream the length variable is invalid - length = null; - } + + params = null; + if (isArray(paramsArray) && (i in paramsArray)) + params = paramsArray[i]; + stream = this.makeFilter(stream, filter.name, length, params); + // after the first stream the length variable is invalid + length = null; } } return stream; }, - makeFilter: function parserMakeFilter(stream, name, length, params) { + makeFilter: function Parser_makeFilter(stream, name, length, params) { if (name == 'FlateDecode' || name == 'Fl') { if (params) { return new PredictorStream(new FlateStream(stream), params); @@ -269,7 +265,7 @@ var Lexer = (function LexerClosure() { this.stream = stream; } - Lexer.isSpace = function lexerIsSpace(ch) { + Lexer.isSpace = function Lexer_isSpace(ch) { return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a'; }; @@ -304,7 +300,7 @@ var Lexer = (function LexerClosure() { } Lexer.prototype = { - getNumber: function lexerGetNumber(ch) { + getNumber: function Lexer_getNumber(ch) { var floating = false; var str = ch; var stream = this.stream; @@ -332,7 +328,7 @@ var Lexer = (function LexerClosure() { error('Invalid floating point number: ' + value); return value; }, - getString: function lexerGetString() { + getString: function Lexer_getString() { var numParen = 1; var done = false; var str = ''; @@ -416,7 +412,7 @@ var Lexer = (function LexerClosure() { } while (!done); return str; }, - getName: function lexerGetName(ch) { + getName: function Lexer_getName(ch) { var str = ''; var stream = this.stream; while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { @@ -443,7 +439,7 @@ var Lexer = (function LexerClosure() { str.length); return new Name(str); }, - getHexString: function lexerGetHexString(ch) { + getHexString: function Lexer_getHexString(ch) { var str = ''; var stream = this.stream; for (;;) { @@ -472,7 +468,7 @@ var Lexer = (function LexerClosure() { } return str; }, - getObj: function lexerGetObj() { + getObj: function Lexer_getObj() { // skip whitespace and comments var comment = false; var stream = this.stream; @@ -526,17 +522,15 @@ var Lexer = (function LexerClosure() { // fall through case ')': error('Illegal character: ' + ch); - return Error; } // command var str = ch; while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { stream.skip(); - if (str.length == 128) { + if (str.length == 128) error('Command token too long: ' + str.length); - break; - } + str += ch; } if (str == 'true') @@ -547,7 +541,7 @@ var Lexer = (function LexerClosure() { return null; return Cmd.get(str); }, - skipToNextLine: function lexerSkipToNextLine() { + skipToNextLine: function Lexer_skipToNextLine() { var stream = this.stream; while (true) { var ch = stream.getChar(); @@ -560,7 +554,7 @@ var Lexer = (function LexerClosure() { } } }, - skip: function lexerSkip() { + skip: function Lexer_skip() { this.stream.skip(); } }; @@ -570,7 +564,7 @@ var Lexer = (function LexerClosure() { var Linearization = (function LinearizationClosure() { function Linearization(stream) { - this.parser = new Parser(new Lexer(stream), false); + this.parser = new Parser(new Lexer(stream), false, null); var obj1 = this.parser.getObj(); var obj2 = this.parser.getObj(); var obj3 = this.parser.getObj(); @@ -584,7 +578,7 @@ var Linearization = (function LinearizationClosure() { } Linearization.prototype = { - getInt: function linearizationGetInt(name) { + getInt: function Linearization_getInt(name) { var linDict = this.linDict; var obj; if (isDict(linDict) && @@ -593,9 +587,8 @@ var Linearization = (function LinearizationClosure() { return obj; } error('"' + name + '" field in linearization table is invalid'); - return 0; }, - getHint: function linearizationGetHint(index) { + getHint: function Linearization_getHint(index) { var linDict = this.linDict; var obj1, obj2; if (isDict(linDict) && @@ -606,7 +599,6 @@ var Linearization = (function LinearizationClosure() { return obj2; } error('Hints table in linearization table is invalid: ' + index); - return 0; }, get length() { if (!isDict(this.linDict)) diff --git a/src/pattern.js b/src/pattern.js index dff2a5b44..7659f5436 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -17,17 +17,17 @@ var Pattern = (function PatternClosure() { Pattern.prototype = { // Input: current Canvas context // Output: the appropriate fillStyle or strokeStyle - getPattern: function pattern_getStyle(ctx) { + getPattern: function Pattern_getPattern(ctx) { error('Should not call Pattern.getStyle: ' + ctx); } }; - Pattern.shadingFromIR = function pattern_shadingFromIR(ctx, raw) { - return Shadings[raw[0]].fromIR(ctx, raw); + Pattern.shadingFromIR = function Pattern_shadingFromIR(raw) { + return Shadings[raw[0]].fromIR(raw); }; - Pattern.parseShading = function pattern_shading(shading, matrix, xref, - res, ctx) { + Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref, + res) { var dict = isStream(shading) ? shading.dict : shading; var type = dict.get('ShadingType'); @@ -36,7 +36,7 @@ var Pattern = (function PatternClosure() { case PatternType.AXIAL: case PatternType.RADIAL: // Both radial and axial shadings are handled by RadialAxial shading. - return new Shadings.RadialAxial(dict, matrix, xref, res, ctx); + return new Shadings.RadialAxial(dict, matrix, xref, res); default: return new Shadings.Dummy(); } @@ -79,10 +79,9 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.extendEnd = extendEnd; var fnObj = dict.get('Function'); - fnObj = xref.fetchIfRef(fnObj); if (isArray(fnObj)) error('No support for array of functions'); - else if (!isPDFFunction(fnObj)) + if (!isPDFFunction(fnObj)) error('Invalid function'); var fn = PDFFunction.parse(xref, fnObj); @@ -102,40 +101,44 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.colorStops = colorStops; } - RadialAxial.fromIR = function radialAxialShadingGetIR(ctx, raw) { + RadialAxial.fromIR = function RadialAxial_fromIR(raw) { var type = raw[1]; var colorStops = raw[2]; var p0 = raw[3]; var p1 = raw[4]; var r0 = raw[5]; var r1 = raw[6]; + return { + type: 'Pattern', + getPattern: function(ctx) { + var curMatrix = ctx.mozCurrentTransform; + if (curMatrix) { + var userMatrix = ctx.mozCurrentTransformInverse; - var curMatrix = ctx.mozCurrentTransform; - if (curMatrix) { - var userMatrix = ctx.mozCurrentTransformInverse; + p0 = Util.applyTransform(p0, curMatrix); + p0 = Util.applyTransform(p0, userMatrix); - p0 = Util.applyTransform(p0, curMatrix); - p0 = Util.applyTransform(p0, userMatrix); + p1 = Util.applyTransform(p1, curMatrix); + p1 = Util.applyTransform(p1, userMatrix); + } - p1 = Util.applyTransform(p1, curMatrix); - p1 = Util.applyTransform(p1, userMatrix); - } + var grad; + if (type == PatternType.AXIAL) + grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + else if (type == PatternType.RADIAL) + grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); - var grad; - if (type == PatternType.AXIAL) - grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); - else if (type == PatternType.RADIAL) - grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); - - for (var i = 0, ii = colorStops.length; i < ii; ++i) { - var c = colorStops[i]; - grad.addColorStop(c[0], c[1]); - } - return grad; + for (var i = 0, ii = colorStops.length; i < ii; ++i) { + var c = colorStops[i]; + grad.addColorStop(c[0], c[1]); + } + return grad; + } + }; }; RadialAxial.prototype = { - getIR: function radialAxialShadingGetIR() { + getIR: function RadialAxial_getIR() { var coordsArr = this.coordsArr; var type = this.shadingType; if (type == PatternType.AXIAL) { @@ -170,12 +173,12 @@ Shadings.Dummy = (function DummyClosure() { this.type = 'Pattern'; } - Dummy.fromIR = function dummyShadingFromIR() { + Dummy.fromIR = function Dummy_fromIR() { return 'hotpink'; }; Dummy.prototype = { - getIR: function dummyShadingGetIR() { + getIR: function Dummy_getIR() { return ['Dummy']; } }; @@ -190,7 +193,7 @@ var TilingPattern = (function TilingPatternClosure() { var MAX_PATTERN_SIZE = 512; function TilingPattern(IR, color, ctx, objs) { - var IRQueue = IR[2]; + var operatorList = IR[2]; this.matrix = IR[3]; var bbox = IR[4]; var xstep = IR[5]; @@ -222,7 +225,7 @@ var TilingPattern = (function TilingPatternClosure() { width = height = MAX_PATTERN_SIZE; } - var tmpCanvas = new ScratchCanvas(width, height); + var tmpCanvas = createScratchCanvas(width, height); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext('2d'); @@ -259,12 +262,12 @@ var TilingPattern = (function TilingPatternClosure() { graphics.endPath(); } - graphics.executeIRQueue(IRQueue); + graphics.executeOperatorList(operatorList); this.canvas = tmpCanvas; } - TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) { + TilingPattern.getIR = function TilingPattern_getIR(operatorList, dict, args) { var matrix = dict.get('Matrix'); var bbox = dict.get('BBox'); var xstep = dict.get('XStep'); @@ -272,12 +275,12 @@ var TilingPattern = (function TilingPatternClosure() { var paintType = dict.get('PaintType'); return [ - 'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType + 'TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, paintType ]; }; TilingPattern.prototype = { - getPattern: function tiling_getPattern() { + getPattern: function TilingPattern_getPattern() { var matrix = this.matrix; var curMatrix = this.curMatrix; var ctx = this.ctx; diff --git a/src/stream.js b/src/stream.js index 610a54d38..48c462fb2 100644 --- a/src/stream.js +++ b/src/stream.js @@ -18,14 +18,14 @@ var Stream = (function StreamClosure() { get length() { return this.end - this.start; }, - getByte: function stream_getByte() { + getByte: function Stream_getByte() { if (this.pos >= this.end) return null; return this.bytes[this.pos++]; }, // returns subarray of original buffer // should only be read - getBytes: function stream_getBytes(length) { + getBytes: function Stream_getBytes(length) { var bytes = this.bytes; var pos = this.pos; var strEnd = this.end; @@ -40,28 +40,28 @@ var Stream = (function StreamClosure() { this.pos = end; return bytes.subarray(pos, end); }, - lookChar: function stream_lookChar() { + lookChar: function Stream_lookChar() { if (this.pos >= this.end) return null; return String.fromCharCode(this.bytes[this.pos]); }, - getChar: function stream_getChar() { + getChar: function Stream_getChar() { if (this.pos >= this.end) return null; return String.fromCharCode(this.bytes[this.pos++]); }, - skip: function stream_skip(n) { + skip: function Stream_skip(n) { if (!n) n = 1; this.pos += n; }, - reset: function stream_reset() { + reset: function Stream_reset() { this.pos = this.start; }, - moveStart: function stream_moveStart() { + moveStart: function Stream_moveStart() { this.start = this.pos; }, - makeSubStream: function stream_makeSubstream(start, length, dict) { + makeSubStream: function Stream_makeSubStream(start, length, dict) { return new Stream(this.bytes.buffer, start, length, dict); }, isStream: true @@ -94,7 +94,7 @@ var DecodeStream = (function DecodeStreamClosure() { } DecodeStream.prototype = { - ensureBuffer: function decodestream_ensureBuffer(requested) { + ensureBuffer: function DecodeStream_ensureBuffer(requested) { var buffer = this.buffer; var current = buffer ? buffer.byteLength : 0; if (requested < current) @@ -107,7 +107,7 @@ var DecodeStream = (function DecodeStreamClosure() { buffer2[i] = buffer[i]; return (this.buffer = buffer2); }, - getByte: function decodestream_getByte() { + getByte: function DecodeStream_getByte() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) @@ -116,7 +116,7 @@ var DecodeStream = (function DecodeStreamClosure() { } return this.buffer[this.pos++]; }, - getBytes: function decodestream_getBytes(length) { + getBytes: function DecodeStream_getBytes(length) { var end, pos = this.pos; if (length) { @@ -144,7 +144,7 @@ var DecodeStream = (function DecodeStreamClosure() { this.pos = end; return this.buffer.subarray(pos, end); }, - lookChar: function decodestream_lookChar() { + lookChar: function DecodeStream_lookChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) @@ -153,7 +153,7 @@ var DecodeStream = (function DecodeStreamClosure() { } return String.fromCharCode(this.buffer[this.pos]); }, - getChar: function decodestream_getChar() { + getChar: function DecodeStream_getChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) @@ -162,18 +162,18 @@ var DecodeStream = (function DecodeStreamClosure() { } return String.fromCharCode(this.buffer[this.pos++]); }, - makeSubStream: function decodestream_makeSubstream(start, length, dict) { + makeSubStream: function DecodeStream_makeSubStream(start, length, dict) { var end = start + length; while (this.bufferLength <= end && !this.eof) this.readBlock(); return new Stream(this.buffer, start, length, dict); }, - skip: function decodestream_skip(n) { + skip: function DecodeStream_skip(n) { if (!n) n = 1; this.pos += n; }, - reset: function decodestream_reset() { + reset: function DecodeStream_reset() { this.pos = 0; } }; @@ -188,14 +188,14 @@ var FakeStream = (function FakeStreamClosure() { } FakeStream.prototype = Object.create(DecodeStream.prototype); - FakeStream.prototype.readBlock = function fakeStreamReadBlock() { + FakeStream.prototype.readBlock = function FakeStream_readBlock() { var bufferLength = this.bufferLength; bufferLength += 1024; var buffer = this.ensureBuffer(bufferLength); this.bufferLength = bufferLength; }; - FakeStream.prototype.getBytes = function fakeStreamGetBytes(length) { + FakeStream.prototype.getBytes = function FakeStream_getBytes(length) { var end, pos = this.pos; if (length) { @@ -368,7 +368,7 @@ var FlateStream = (function FlateStreamClosure() { FlateStream.prototype = Object.create(DecodeStream.prototype); - FlateStream.prototype.getBits = function flateStreamGetBits(bits) { + FlateStream.prototype.getBits = function FlateStream_getBits(bits) { var codeSize = this.codeSize; var codeBuf = this.codeBuf; var bytes = this.bytes; @@ -388,7 +388,7 @@ var FlateStream = (function FlateStreamClosure() { return b; }; - FlateStream.prototype.getCode = function flateStreamGetCode(table) { + FlateStream.prototype.getCode = function FlateStream_getCode(table) { var codes = table[0]; var maxLen = table[1]; var codeSize = this.codeSize; @@ -453,7 +453,7 @@ var FlateStream = (function FlateStreamClosure() { return [codes, maxLen]; }; - FlateStream.prototype.readBlock = function flateStreamReadBlock() { + FlateStream.prototype.readBlock = function FlateStream_readBlock() { // read block header var hdr = this.getBits(3); if (hdr & 1) @@ -623,7 +623,6 @@ var PredictorStream = (function PredictorStreamClosure() { var bufferLength = this.bufferLength; var buffer = this.ensureBuffer(bufferLength + rowBytes); - var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var bits = this.bits; var colors = this.colors; @@ -632,6 +631,7 @@ var PredictorStream = (function PredictorStreamClosure() { var inbuf = 0, outbuf = 0; var inbits = 0, outbits = 0; + var pos = bufferLength; if (bits === 1) { for (var i = 0; i < rowBytes; ++i) { @@ -639,19 +639,21 @@ var PredictorStream = (function PredictorStreamClosure() { inbuf = (inbuf << 8) | c; // bitwise addition is exclusive or // first shift inbuf and then add - currentRow[i] = (c ^ (inbuf >> colors)) & 0xFF; + buffer[pos++] = (c ^ (inbuf >> colors)) & 0xFF; // truncate inbuf (assumes colors < 16) inbuf &= 0xFFFF; } } else if (bits === 8) { for (var i = 0; i < colors; ++i) - currentRow[i] = rawBytes[i]; - for (; i < rowBytes; ++i) - currentRow[i] = currentRow[i - colors] + rawBytes[i]; + buffer[pos++] = rawBytes[i]; + for (; i < rowBytes; ++i) { + buffer[pos] = buffer[pos - colors] + rawBytes[i]; + pos++; + } } else { var compArray = new Uint8Array(colors + 1); var bitMask = (1 << bits) - 1; - var j = 0, k = 0; + var j = 0, k = bufferLength; var columns = this.columns; for (var i = 0; i < columns; ++i) { for (var kk = 0; kk < colors; ++kk) { @@ -665,13 +667,13 @@ var PredictorStream = (function PredictorStreamClosure() { outbuf = (outbuf << bits) | compArray[kk]; outbits += bits; if (outbits >= 8) { - currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF; + buffer[k++] = (outbuf >> (outbits - 8)) & 0xFF; outbits -= 8; } } } if (outbits > 0) { - currentRow[k++] = (outbuf << (8 - outbits)) + + buffer[k++] = (outbuf << (8 - outbits)) + (inbuf & ((1 << (8 - outbits)) - 1)); } } @@ -690,32 +692,35 @@ var PredictorStream = (function PredictorStreamClosure() { var bufferLength = this.bufferLength; var buffer = this.ensureBuffer(bufferLength + rowBytes); - var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); if (prevRow.length == 0) prevRow = new Uint8Array(rowBytes); + var j = bufferLength; switch (predictor) { case 0: for (var i = 0; i < rowBytes; ++i) - currentRow[i] = rawBytes[i]; + buffer[j++] = rawBytes[i]; break; case 1: for (var i = 0; i < pixBytes; ++i) - currentRow[i] = rawBytes[i]; - for (; i < rowBytes; ++i) - currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF; + buffer[j++] = rawBytes[i]; + for (; i < rowBytes; ++i) { + buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xFF; + j++; + } break; case 2: for (var i = 0; i < rowBytes; ++i) - currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF; + buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xFF; break; case 3: for (var i = 0; i < pixBytes; ++i) - currentRow[i] = (prevRow[i] >> 1) + rawBytes[i]; + buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; for (; i < rowBytes; ++i) { - currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) >> 1) + + buffer[j] = (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xFF; + j++; } break; case 4: @@ -724,12 +729,12 @@ var PredictorStream = (function PredictorStreamClosure() { for (var i = 0; i < pixBytes; ++i) { var up = prevRow[i]; var c = rawBytes[i]; - currentRow[i] = up + c; + buffer[j++] = up + c; } for (; i < rowBytes; ++i) { var up = prevRow[i]; var upLeft = prevRow[i - pixBytes]; - var left = currentRow[i - pixBytes]; + var left = buffer[j - pixBytes]; var p = left + up - upLeft; var pa = p - left; @@ -744,11 +749,11 @@ var PredictorStream = (function PredictorStreamClosure() { var c = rawBytes[i]; if (pa <= pb && pa <= pc) - currentRow[i] = left + c; + buffer[j++] = left + c; else if (pb <= pc) - currentRow[i] = up + c; + buffer[j++] = up + c; else - currentRow[i] = upLeft + c; + buffer[j++] = upLeft + c; } break; default: @@ -818,31 +823,35 @@ var JpegStream = (function JpegStreamClosure() { JpegStream.prototype = Object.create(DecodeStream.prototype); - JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { + JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) { if (this.bufferLength) return; - var jpegImage = new JpegImage(); - if (this.colorTransform != -1) - jpegImage.colorTransform = this.colorTransform; - jpegImage.parse(this.bytes); - var width = jpegImage.width; - var height = jpegImage.height; - var data = jpegImage.getData(width, height); - this.buffer = data; - this.bufferLength = data.length; + try { + var jpegImage = new JpegImage(); + if (this.colorTransform != -1) + jpegImage.colorTransform = this.colorTransform; + jpegImage.parse(this.bytes); + var width = jpegImage.width; + var height = jpegImage.height; + var data = jpegImage.getData(width, height); + this.buffer = data; + this.bufferLength = data.length; + } catch (e) { + error('JPEG error: ' + e); + } }; - JpegStream.prototype.getIR = function jpegStreamGetIR() { + JpegStream.prototype.getIR = function JpegStream_getIR() { return bytesToString(this.bytes); }; - JpegStream.prototype.getChar = function jpegStreamGetChar() { + JpegStream.prototype.getChar = function JpegStream_getChar() { error('internal error: getChar is not valid on JpegStream'); }; /** * Checks if the image can be decoded and displayed by the browser without any * further processing such as color space conversions. */ - JpegStream.prototype.isNativelySupported = function isNativelySupported(xref, - res) { + JpegStream.prototype.isNativelySupported = + function JpegStream_isNativelySupported(xref, res) { var cs = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res); // when bug 674619 lands, let's check if browser can do // normal cmyk and then we won't need to decode in JS @@ -856,8 +865,8 @@ var JpegStream = (function JpegStreamClosure() { /** * Checks if the image can be decoded by the browser. */ - JpegStream.prototype.isNativelyDecodable = function isNativelyDecodable(xref, - res) { + JpegStream.prototype.isNativelyDecodable = + function JpegStream_isNativelyDecodable(xref, res) { var cs = ColorSpace.parse(this.dict.get('ColorSpace'), xref, res); var numComps = cs.numComps; if (numComps == 1 || numComps == 3) @@ -883,7 +892,7 @@ var JpxStream = (function JpxStreamClosure() { JpxStream.prototype = Object.create(DecodeStream.prototype); - JpxStream.prototype.ensureBuffer = function jpxStreamEnsureBuffer(req) { + JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) { if (this.bufferLength) return; @@ -963,7 +972,7 @@ var JpxStream = (function JpxStreamClosure() { this.buffer = data; this.bufferLength = data.length; }; - JpxStream.prototype.getChar = function jpxStreamGetChar() { + JpxStream.prototype.getChar = function JpxStream_getChar() { error('internal error: getChar is not valid on JpxStream'); }; @@ -983,7 +992,7 @@ var DecryptStream = (function DecryptStreamClosure() { DecryptStream.prototype = Object.create(DecodeStream.prototype); - DecryptStream.prototype.readBlock = function decryptStreamReadBlock() { + DecryptStream.prototype.readBlock = function DecryptStream_readBlock() { var chunk = this.str.getBytes(chunkSize); if (!chunk || chunk.length == 0) { this.eof = true; @@ -1014,7 +1023,7 @@ var Ascii85Stream = (function Ascii85StreamClosure() { Ascii85Stream.prototype = Object.create(DecodeStream.prototype); - Ascii85Stream.prototype.readBlock = function ascii85StreamReadBlock() { + Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() { var tildaCode = '~'.charCodeAt(0); var zCode = 'z'.charCodeAt(0); var str = this.str; @@ -1109,7 +1118,7 @@ var AsciiHexStream = (function AsciiHexStreamClosure() { AsciiHexStream.prototype = Object.create(DecodeStream.prototype); - AsciiHexStream.prototype.readBlock = function asciiHexStreamReadBlock() { + AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() { var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n, decodeLength, buffer, bufferLength, i, length; @@ -1152,7 +1161,7 @@ var RunLengthStream = (function RunLengthStreamClosure() { RunLengthStream.prototype = Object.create(DecodeStream.prototype); - RunLengthStream.prototype.readBlock = function runLengthStreamReadBlock() { + RunLengthStream.prototype.readBlock = function RunLengthStream_readBlock() { // The repeatHeader has following format. The first byte defines type of run // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes // (in addition to the second byte from the header), n = 129 through 255 - @@ -1662,7 +1671,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { CCITTFaxStream.prototype = Object.create(DecodeStream.prototype); - CCITTFaxStream.prototype.readBlock = function ccittFaxStreamReadBlock() { + CCITTFaxStream.prototype.readBlock = function CCITTFaxStream_readBlock() { while (!this.eof) { var c = this.lookChar(); this.buf = EOF; @@ -1720,7 +1729,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.codingPos = codingPos; }; - CCITTFaxStream.prototype.lookChar = function ccittFaxStreamLookChar() { + CCITTFaxStream.prototype.lookChar = function CCITTFaxStream_lookChar() { if (this.buf != EOF) return this.buf; @@ -2047,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]; } @@ -2131,7 +2140,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { return 1; }; - CCITTFaxStream.prototype.lookBits = function ccittFaxStreamLookBits(n) { + CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) { var c; while (this.inputBits < n) { if ((c = this.str.getByte()) == null) { @@ -2146,7 +2155,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n)); }; - CCITTFaxStream.prototype.eatBits = function ccittFaxStreamEatBits(n) { + CCITTFaxStream.prototype.eatBits = function CCITTFaxStream_eatBits(n) { if ((this.inputBits -= n) < 0) this.inputBits = 0; }; @@ -2183,7 +2192,7 @@ var LZWStream = (function LZWStreamClosure() { LZWStream.prototype = Object.create(DecodeStream.prototype); - LZWStream.prototype.readBits = function lzwStreamReadBits(n) { + LZWStream.prototype.readBits = function LZWStream_readBits(n) { var bitsCached = this.bitsCached; var cachedData = this.cachedData; while (bitsCached < n) { @@ -2201,7 +2210,7 @@ var LZWStream = (function LZWStreamClosure() { return (cachedData >>> bitsCached) & ((1 << n) - 1); }; - LZWStream.prototype.readBlock = function lzwStreamReadBlock() { + LZWStream.prototype.readBlock = function LZWStream_readBlock() { var blockSize = 512; var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize; var i, j, q; diff --git a/src/util.js b/src/util.js index 99b422296..de7f3c1d5 100644 --- a/src/util.js +++ b/src/util.js @@ -78,21 +78,99 @@ var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; var Util = (function UtilClosure() { function Util() {} - Util.makeCssRgb = function makergb(r, g, b) { + + Util.makeCssRgb = function Util_makeCssRgb(r, g, b) { var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; }; - Util.makeCssCmyk = function makecmyk(c, m, y, k) { + + Util.makeCssCmyk = function Util_makeCssCmyk(c, m, y, k) { c = (new DeviceCmykCS()).getRgb([c, m, y, k]); var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0; return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; }; - Util.applyTransform = function apply(p, m) { + + // For 2d affine transforms + Util.applyTransform = function Util_applyTransform(p, m) { var xt = p[0] * m[0] + p[1] * m[2] + m[4]; var yt = p[0] * m[1] + p[1] * m[3] + m[5]; return [xt, yt]; }; + // Apply a generic 3d matrix M on a 3-vector v: + // | a b c | | X | + // | d e f | x | Y | + // | g h i | | Z | + // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i], + // with v as [X,Y,Z] + Util.apply3dTransform = function Util_apply3dTransform(m, v) { + return [ + m[0] * v[0] + m[1] * v[1] + m[2] * v[2], + m[3] * v[0] + m[4] * v[1] + m[5] * v[2], + m[6] * v[0] + m[7] * v[1] + m[8] * v[2] + ]; + } + + // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) + // For coordinate systems whose origin lies in the bottom-left, this + // means normalization to (BL,TR) ordering. For systems with origin in the + // top-left, this means (TL,BR) ordering. + Util.normalizeRect = function Util_normalizeRect(rect) { + var r = rect.slice(0); // clone rect + if (rect[0] > rect[2]) { + r[0] = rect[2]; + r[2] = rect[0]; + } + if (rect[1] > rect[3]) { + r[1] = rect[3]; + r[3] = rect[1]; + } + return r; + } + + // Returns a rectangle [x1, y1, x2, y2] corresponding to the + // intersection of rect1 and rect2. If no intersection, returns 'false' + // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2] + Util.intersect = function Util_intersect(rect1, rect2) { + function compare(a, b) { + return a - b; + }; + + // Order points along the axes + var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare), + orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare), + result = []; + + rect1 = Util.normalizeRect(rect1); + rect2 = Util.normalizeRect(rect2); + + // X: first and second points belong to different rectangles? + if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) || + (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) { + // Intersection must be between second and third points + result[0] = orderedX[1]; + result[2] = orderedX[2]; + } else { + return false; + } + + // Y: first and second points belong to different rectangles? + if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) || + (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) { + // Intersection must be between second and third points + result[1] = orderedY[1]; + result[3] = orderedY[2]; + } else { + return false; + } + + return result; + } + + Util.sign = function Util_sign(num) { + return num < 0 ? -1 : 1; + }; + return Util; })(); @@ -226,7 +304,7 @@ var Promise = (function PromiseClosure() { * @param {Promise[]} promises Array of promises to wait for. * @return {Promise} New dependant promise. */ - Promise.all = function(promises) { + Promise.all = function Promise_all(promises) { var deferred = new Promise(); var unresolved = promises.length; var results = []; @@ -255,8 +333,8 @@ var Promise = (function PromiseClosure() { return; } if (this._data !== EMPTY_PROMISE) { - throw 'Promise ' + this.name + - ': Cannot set the data of a promise twice'; + error('Promise ' + this.name + + ': Cannot set the data of a promise twice'); } this._data = value; this.hasData = true; @@ -268,12 +346,12 @@ var Promise = (function PromiseClosure() { get data() { if (this._data === EMPTY_PROMISE) { - throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; + error('Promise ' + this.name + ': Cannot get data that isn\'t set'); } return this._data; }, - onData: function promiseOnData(callback) { + onData: function Promise_onData(callback) { if (this._data !== EMPTY_PROMISE) { callback(this._data); } else { @@ -281,12 +359,12 @@ var Promise = (function PromiseClosure() { } }, - resolve: function promiseResolve(data) { + resolve: function Promise_resolve(data) { if (this.isResolved) { - throw 'A Promise can be resolved only once ' + this.name; + error('A Promise can be resolved only once ' + this.name); } if (this.isRejected) { - throw 'The Promise was already rejected ' + this.name; + error('The Promise was already rejected ' + this.name); } this.isResolved = true; @@ -298,12 +376,12 @@ var Promise = (function PromiseClosure() { } }, - reject: function proimseReject(reason) { + reject: function Promise_reject(reason) { if (this.isRejected) { - throw 'A Promise can be rejected only once ' + this.name; + error('A Promise can be rejected only once ' + this.name); } if (this.isResolved) { - throw 'The Promise was already resolved ' + this.name; + error('The Promise was already resolved ' + this.name); } this.isRejected = true; @@ -315,16 +393,16 @@ var Promise = (function PromiseClosure() { } }, - then: function promiseThen(callback, errback) { + then: function Promise_then(callback, errback) { if (!callback) { - throw 'Requiring callback' + this.name; + error('Requiring callback' + this.name); } // If the promise is already resolved, call the callback directly. if (this.isResolved) { var data = this.data; callback.call(null, data); - } else if (this.isRejected && errorback) { + } else if (this.isRejected && errback) { var error = this.error; errback.call(null, error); } else { @@ -338,3 +416,55 @@ var Promise = (function PromiseClosure() { return Promise; })(); +var StatTimer = (function StatTimerClosure() { + function rpad(str, pad, length) { + while (str.length < length) + str += pad; + return str; + } + function StatTimer() { + this.started = {}; + this.times = []; + this.enabled = true; + } + StatTimer.prototype = { + time: function StatTimer_time(name) { + if (!this.enabled) + return; + if (name in this.started) + throw 'Timer is already running for ' + name; + this.started[name] = Date.now(); + }, + timeEnd: function StatTimer_timeEnd(name) { + if (!this.enabled) + return; + if (!(name in this.started)) + throw 'Timer has not been started for ' + name; + this.times.push({ + 'name': name, + 'start': this.started[name], + 'end': Date.now() + }); + // Remove timer from started so it can be called again. + delete this.started[name]; + }, + toString: function StatTimer_toString() { + var times = this.times; + var out = ''; + // Find the longest name for padding purposes. + var longest = 0; + for (var i = 0, ii = times.length; i < ii; ++i) { + var name = times[i]['name']; + if (name.length > longest) + longest = name.length; + } + for (var i = 0, ii = times.length; i < ii; ++i) { + var span = times[i]; + var duration = span.end - span.start; + out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; + } + return out; + } + }; + return StatTimer; +})(); diff --git a/src/utils/fonts_utils.js b/src/utils/fonts_utils.js index 2a1f0ea72..65c02fce2 100644 --- a/src/utils/fonts_utils.js +++ b/src/utils/fonts_utils.js @@ -122,9 +122,9 @@ function readFontDictData(aString, aMap) { token = ''; var parsed = false; while (!parsed) { - var byte = aString[i++]; + var octet = aString[i++]; - var nibbles = [parseInt(byte / 16, 10), parseInt(byte % 16, 10)]; + var nibbles = [parseInt(octet / 16, 10), parseInt(octet % 16, 10)]; for (var j = 0; j < nibbles.length; j++) { var nibble = nibbles[j]; switch (nibble) { @@ -336,7 +336,7 @@ var Type2Parser = function type2Parser(aFilePath) { var privateDict = []; for (var i = 0; i < priv.size; i++) privateDict.push(aStream.getByte()); - dump('private:' + privateDict); + dump('privateData:' + privateDict); parseAsToken(privateDict, CFFDictPrivateDataMap); for (var p in font.map) diff --git a/src/worker.js b/src/worker.js index 6ea49d66b..b75fc66e8 100644 --- a/src/worker.js +++ b/src/worker.js @@ -26,7 +26,7 @@ function MessageHandler(name, comObj) { delete callbacks[callbackId]; callback(data.data); } else { - throw 'Cannot resolve callback ' + callbackId; + error('Cannot resolve callback ' + callbackId); } } else if (data.action in ah) { var action = ah[data.action]; @@ -44,7 +44,7 @@ function MessageHandler(name, comObj) { action[0].call(action[1], data.data); } } else { - throw 'Unkown action from worker: ' + data.action; + error('Unkown action from worker: ' + data.action); } }; } @@ -53,7 +53,7 @@ MessageHandler.prototype = { on: function messageHandlerOn(actionName, handler, scope) { var ah = this.actionHandler; if (ah[actionName]) { - throw 'There is already an actionName called "' + actionName + '"'; + error('There is already an actionName called "' + actionName + '"'); } ah[actionName] = [handler, scope]; }, @@ -79,7 +79,7 @@ MessageHandler.prototype = { var WorkerMessageHandler = { setup: function wphSetup(handler) { - var pdfDoc = null; + var pdfModel = null; handler.on('test', function wphSetupTest(data) { handler.send('test', data instanceof Uint8Array); @@ -88,7 +88,7 @@ var WorkerMessageHandler = { handler.on('doc', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. - pdfDoc = new PDFDocModel(new Stream(data)); + pdfModel = new PDFDocModel(new Stream(data)); }); handler.on('page_request', function wphSetupPageRequest(pageNum) { @@ -103,17 +103,33 @@ var WorkerMessageHandler = { var start = Date.now(); var dependency = []; - var IRQueue = null; + var operatorList = null; try { - var page = pdfDoc.getPage(pageNum); + var page = pdfModel.getPage(pageNum); // Pre compile the pdf page and fetch the fonts/images. - IRQueue = page.getIRQueue(handler, dependency); + operatorList = page.getOperatorList(handler, dependency); } catch (e) { + var minimumStackMessage = + 'worker.js: while trying to getPage() and getOperatorList()'; + // Turn the error into an obj that can be serialized - e = { - message: typeof e === 'object' ? e.message : e, - stack: typeof e === 'object' ? e.stack : null - }; + if (typeof e === 'string') { + e = { + message: e, + stack: minimumStackMessage + }; + } else if (typeof e === 'object') { + e = { + message: e.message || e.toString(), + stack: e.stack || minimumStackMessage + }; + } else { + e = { + message: 'Unknown exception type: ' + (typeof e), + stack: minimumStackMessage + }; + } + handler.send('page_error', { pageNum: pageNum, error: e @@ -121,8 +137,8 @@ var WorkerMessageHandler = { return; } - console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum, - Date.now() - start, IRQueue.fnArray.length); + console.log('page=%d - getOperatorList: time=%dms, len=%d', pageNum, + Date.now() - start, operatorList.fnArray.length); // Filter the dependecies for fonts. var fonts = {}; @@ -135,62 +151,13 @@ var WorkerMessageHandler = { handler.send('page', { pageNum: pageNum, - IRQueue: IRQueue, + operatorList: operatorList, depFonts: Object.keys(fonts) }); }, this); - handler.on('font', function wphSetupFont(data) { - var objId = data[0]; - var name = data[1]; - var file = data[2]; - var properties = data[3]; - - var font = { - name: name, - file: file, - properties: properties - }; - - // Some fonts don't have a file, e.g. the build in ones like Arial. - if (file) { - var fontFileDict = new Dict(); - fontFileDict.map = file.dict.map; - - var fontFile = new Stream(file.bytes, file.start, - file.end - file.start, fontFileDict); - - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - font.file = new FlateStream(fontFile); - } else { - font.file = fontFile; - } - } - - var obj = new Font(font.name, font.file, font.properties); - - var str = ''; - var objData = obj.data; - if (objData) { - var length = objData.length; - for (var j = 0; j < length; ++j) - str += String.fromCharCode(objData[j]); - } - - obj.str = str; - - // Remove the data array form the font object, as it's not needed - // anymore as we sent over the ready str. - delete obj.data; - - handler.send('font_ready', [objId, obj]); - }); - handler.on('extract_text', function wphExtractText() { - var numPages = pdfDoc.numPages; + var numPages = pdfModel.numPages; var index = []; var start = Date.now(); @@ -203,12 +170,12 @@ var WorkerMessageHandler = { } var textContent = ''; - try { - var page = pdfDoc.getPage(pageNum); + // try { + var page = pdfModel.getPage(pageNum); textContent = page.extractTextContent(); - } catch (e) { - // Skip errored pages - } + // } catch (e) { + // // Skip errored pages + // } index.push(textContent); @@ -241,6 +208,7 @@ var workerConsole = { action: 'console_error', data: args }); + throw 'pdf.js execution error'; }, time: function time(name) { @@ -250,7 +218,7 @@ var workerConsole = { timeEnd: function timeEnd(name) { var time = consoleTimer[name]; if (time == null) { - throw 'Unkown timer name ' + name; + error('Unkown timer name ' + name); } this.log('Timer:', name, Date.now() - time); } diff --git a/src/worker_loader.js b/src/worker_loader.js index fc3d7a5f7..69eb1414f 100644 --- a/src/worker_loader.js +++ b/src/worker_loader.js @@ -24,7 +24,8 @@ var files = [ 'stream.js', 'worker.js', '../external/jpgjs/jpg.js', - 'jpx.js' + 'jpx.js', + 'bidi.js' ]; // Load all the files. diff --git a/test/driver.js b/test/driver.js index 5a3263bda..a1dc4b242 100644 --- a/test/driver.js +++ b/test/driver.js @@ -248,16 +248,21 @@ function done() { } } -function sendTaskResult(snapshot, task, failure) { - var result = { browser: browser, - id: task.id, - numPages: task.pdfDoc ? - (task.pageLimit || task.pdfDoc.numPages) : 0, - failure: failure, - file: task.file, - round: task.round, - page: task.pageNum, - snapshot: snapshot }; +function sendTaskResult(snapshot, task, failure, result) { + // Optional result argument is for retrying XHR requests - see below + if (!result) { + result = JSON.stringify({ + browser: browser, + id: task.id, + numPages: task.pdfDoc ? + (task.pageLimit || task.pdfDoc.numPages) : 0, + failure: failure, + file: task.file, + round: task.round, + page: task.pageNum, + snapshot: snapshot + }); + } var r = new XMLHttpRequest(); // (The POST URI is ignored atm.) @@ -266,10 +271,13 @@ function sendTaskResult(snapshot, task, failure) { r.onreadystatechange = function sendTaskResultOnreadystatechange(e) { if (r.readyState == 4) { inFlightRequests--; + // Retry until successful + if (r.status !== 200) + sendTaskResult(null, null, null, result); } }; document.getElementById('inFlightCount').innerHTML = inFlightRequests++; - r.send(JSON.stringify(result)); + r.send(result); } function clear(ctx) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index e0926492b..2a7c27875 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -19,8 +19,16 @@ !issue840.pdf !scan-bad.pdf !freeculture.pdf +!pdfkit_compressed.pdf +!TAMReview.pdf !issue918.pdf +!issue1249.pdf !smaskdim.pdf !type4psfunc.pdf +!issue1350.pdf !S2.pdf !zerowidthline.pdf +!issue1002.pdf +!issue925.pdf +!gradientfill.pdf + diff --git a/test/pdfs/TAMReview.pdf b/test/pdfs/TAMReview.pdf new file mode 100644 index 000000000..dc77653c5 Binary files /dev/null and b/test/pdfs/TAMReview.pdf differ diff --git a/test/pdfs/gradientfill.pdf b/test/pdfs/gradientfill.pdf new file mode 100644 index 000000000..170c05f73 Binary files /dev/null and b/test/pdfs/gradientfill.pdf differ diff --git a/test/pdfs/html5checker.pdf.link b/test/pdfs/html5checker.pdf.link new file mode 100644 index 000000000..8c4552e59 --- /dev/null +++ b/test/pdfs/html5checker.pdf.link @@ -0,0 +1 @@ +http://hsivonen.iki.fi/thesis/html5-conformance-checker.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/issue1049.pdf.link b/test/pdfs/issue1049.pdf.link new file mode 100644 index 000000000..c486dda43 --- /dev/null +++ b/test/pdfs/issue1049.pdf.link @@ -0,0 +1 @@ +http://ernestinefont.com/wp-content/themes/iA3%201.2.1/assets/pdf/ErnestinePro-InfoGuide.pdf diff --git a/test/pdfs/issue1096.pdf.link b/test/pdfs/issue1096.pdf.link new file mode 100644 index 000000000..aa07f14dd --- /dev/null +++ b/test/pdfs/issue1096.pdf.link @@ -0,0 +1 @@ +http://www.faithaliveresources.org/Content/Site135/FilesSamples/105315400440pdf_00000009843.pdf diff --git a/test/pdfs/issue1127.pdf.link b/test/pdfs/issue1127.pdf.link new file mode 100644 index 000000000..2df2304ba --- /dev/null +++ b/test/pdfs/issue1127.pdf.link @@ -0,0 +1 @@ +https://vmp.ethz.ch/pdfs/diplome/vordiplome/Block%201/Algorithmen_%26_Komplexitaet/AlgoKo_f08_Aufg.pdf diff --git a/test/pdfs/issue1133.pdf.link b/test/pdfs/issue1133.pdf.link new file mode 100644 index 000000000..2480ba8f8 --- /dev/null +++ b/test/pdfs/issue1133.pdf.link @@ -0,0 +1 @@ +http://www.cscw2012.org/docs/program.pdf diff --git a/test/pdfs/issue1155.pdf.link b/test/pdfs/issue1155.pdf.link new file mode 100644 index 000000000..13df4e035 --- /dev/null +++ b/test/pdfs/issue1155.pdf.link @@ -0,0 +1 @@ +http://www.madbad.altervista.org/_altervista_ht/2142.pdf diff --git a/test/pdfs/issue1169.pdf.link b/test/pdfs/issue1169.pdf.link new file mode 100644 index 000000000..46559a078 --- /dev/null +++ b/test/pdfs/issue1169.pdf.link @@ -0,0 +1 @@ +http://www.cs.txstate.edu/~mb92/papers/gpgpu11.pdf diff --git a/test/pdfs/issue1243.pdf.link b/test/pdfs/issue1243.pdf.link new file mode 100644 index 000000000..89187f785 --- /dev/null +++ b/test/pdfs/issue1243.pdf.link @@ -0,0 +1 @@ +http://www.nsa.gov/public_info/_files/nash_letters/nash_letters1.pdf diff --git a/test/pdfs/issue1249.pdf b/test/pdfs/issue1249.pdf new file mode 100644 index 000000000..f7bacda02 Binary files /dev/null and b/test/pdfs/issue1249.pdf differ diff --git a/test/pdfs/issue1257.pdf.link b/test/pdfs/issue1257.pdf.link new file mode 100644 index 000000000..5a8bdd15c --- /dev/null +++ b/test/pdfs/issue1257.pdf.link @@ -0,0 +1 @@ +http://hse-econ.fi/tervio/MediocritiesAndSuperstars.pdf 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 +ÿØÿàJFIFHHÿá€ExifMM*JR(‡iZHH @ îÿÛC   +  + + +   ÿÛC + +ÿÀî@"ÿÄ + ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ + ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ +$4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ýü¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(®rúúê VÆÖ ¢IèØÝ×·á]s7°_OªiÎ8´–•$Ç9•ÈôÿkŠ×[¨d0Ë+@ñ´‚Aӊʵ¹ž}bä%üRY‘’ÜŒäqÐçÖ–ÊÞÄè›­e÷¶îX’3üê®a®¯+$3HR3Ä¡T~æ?ñúëh¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠüLŠ/Ú3ã'íñ§áïÃß:îý«j÷QA{â-BÖÚh¯¼•Ž5‡~Üy¨… +vÀë?ðÊŸ·ý“á_­ÿñš?eOù>ÚKýíÿNðW¬|3øg­|`Ö¾7ëz߯ÿŠšWöWõ­ÒÃÃþ'’ÖÚ h¤FEXÙ_n<Ò(P Šô*TpvVI%Ðñ©RUÝÛmõ¶Ç“ÿÃ*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×Ö?ðËkÿGñÃÿ ÿãTÃ-¯ýOÇü,[ÿV~ßÍ}ÆßT_Êÿð#äïøeOÛƒþŽI¿ð¯ÖÿøÍ'ü2¯íÁÿG$ßøWë_üf¾±ÿ†Z_ú8ŸŽøX·ÿ£þhÑÅ|pÿÂÅ¿øÕßÍ}ÁõOî¿üù?þSöàÿ£’oü+õ¿þ3Gü2§íÁÿG$ßøXküf¾°ÿ†Z_ú8ŸŽøX·ÿ£þièâ~8ábßüjoæ¾àú§÷_þ|Ÿÿ «ûpÑÉ7þú×ÿ£þWöàÿ£’oü+õ¯þ3_XÃ-/ýOÇü,[ÿRÿÃ-¯ýOÇü,[ÿQíü×ÜTþëÿÀ“¿á•?nú9&ÿ¿[ÿã4Ã*þÜôrMÿ…~µÿÆkëøe¥ÿ£‰øáÿ…‹ñª?á–‡ýWÇü,[ÿQíü×ÜTþëÿÀ“ÿá•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe¡ÿGñÃÿ ÿãT¿ðËkÿGñÃÿ ÿãT{5÷Õò¿üù?þWößÿ£‘oü+õ¿þ3Iÿ ©ûpÑÉ7þúßÿ¯¬á–×þŽ'ã‡þ-ÿƨÿ†[_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉßðÊŸ·ý“á_­ÿñš?á•nú9&ÿ¿Zÿã5õü2ÚÿÑÄüpÿÂÅ¿øÕ'ü2Ðÿ£Šøáÿ…‹ñª=¿šûƒêŸÝøòü2§íÁÿG$ßøWëüføe_ÛƒþŽI¿ð¯Ö¿øÍ}aÿ ´?èâ¾8ábßüjøe¥ÿ£‰øáÿ…‹ñª=¿šûƒêŸÝøòü2¯íÁÿG$ßøWë_üføeOÛƒþŽI¿ð¯ÖÿøÍ}aÿ ´¿ôq??ð±oþ5Gü2ÒÿÑÄüpÿÂÅ¿øÕßÍ}ÁõOî¿üù?þSöàÿ£’oü+õ¿þ3Iÿ «ûpÑÉ7þú×ÿ¯¬á–‡ýWÇü,[ÿQÿ ´¿ôq??ð±oþ5G·ó_p}Sû¯ÿ>Oÿ†Tý¸?èä›ÿ +ýoÿŒÑÿ ©ûpÑÉ7þúßÿ¯¬?á–‡ýWÇü,[ÿRÿÃ-¯ýOÇü,[ÿQíü×ÜT_Êÿð#äïøe_ÛƒþŽI¿ð¯Ö¿øÍðÊŸ·ý“á_­ÿñšúÃþhÑÅ|pÿÂÅ¿øÕðËCþŽ+ã‡þ-ÿƨöþkîªuÿàGÉÿðÊ¿·ý“á_­ñš?á•nú9&ÿ¿Zÿã5õ‡ü2ÒÿÑÄüpÿÂÅ¿øÕðËKÿGñÃÿ ÿãT{5÷Õ?ºÿð#äÿøeOÛƒþŽI¿ð¯ÖÿøÍðÊŸ·ý“áa­ÿñšúÃþièâ~8ábßüjøe¡ÿGñÃÿ ÿãT{5÷Õ?ºÿð#äÿøeOÛƒþŽI¿ð¯ÖÿøÍðÊŸ·ý“á_­ÿñšúÃþièâ~8ábßüjøe¥ÿ£‰øáÿ…‹ñª=¿šûƒê‹ù_þ|Ÿÿ ©ûpÑÉ7þúßÿ£þSöàÿ£’oü+õ¿þ3_XÿÃ-¯ýOÇü,[ÿRÃ-ú8¯ŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉÿðÊŸ·ý“á_­ÿñš?á•nú9&ÿ¿Zÿã5õ‡ü2ÒÿÑÄüpÿÂÅ¿øÕðËCþŽ+ã‡þ-ÿƨöþkîªuÿàGÉÿðÊŸ·ý“á_­ÿñš?á•?nú9&ÿ¿[ÿã5õü2ÚÿÑÄüpÿÂÅ¿øÕ'ü2ÒÿÑÄüpÿÂÅ¿øÕßÍ}ÁõEü¯ÿ>Oÿ†Tý¸?èä›ÿ +ýoÿŒÑÿ «ûpÑÉ7þú×ÿ¯¬?á–ÇýOÇü,Oÿ£þhÑÅ|pÿÂÅ¿øÕßÍ}ÁõOî¿üù?þSöàÿ£’oü+õ¿þ3Gü2§íÁÿG$ßøWëüf¾°ÿ†Z_ú8ŸŽøX·ÿ£þièâ~8ábßüjoæ¾àú¢þWÿ'ÿÃ*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×ÕÿðËCþŽ'ã‡þGÿS¿á–×þŽ'ã‡þ-ÿƨöþkîª/åøòwü2¯íÁÿG$ßøWë_üføe_ÛƒþŽI¿ð¯ÖÿøÕ}aÿ ´?èâ¾8ábßüj—þmèâ~8ábßüjoæ¾àú§÷_þ|ÿ ©ûpÑÉ7þúßÿ£þSöàÿ£’oü+õ¿þ3_WÿÃ-ú8ŸŽøYþ5Kÿ ´?èâ¾8ábßüjoæ¾àú¢þWÿ'ÿÃ*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×ÖðËCþŽ+ã‡þ-ÿÆ©á–×þŽ'ã‡þ-ÿƨöþkîª/åøòwü2§íÁÿG$ßøWëüføeOÛƒþŽI¿ð¯ÖÿøÍ}cÿ ¶¿ôq??ð±oþ5Gü2ÚÿÑÄüpÿÂÅ¿øÕßÍ}ÁõEü¯ÿ>Nÿ†Tý¸?èä›ÿ oÿŒÑÿ ©ûpÑÉ7þúßÿ¯¬?á–—þŽ'ã‡þ-ÿƨÿ†Z_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉÿðÊŸ·ý“á_­ÿñš?á•?nú9&ÿ¿[ÿã5õ‡ü2ÒÿÑÄüpÿÂÅ¿øÕðËKÿGñÃÿ ÿãT{5÷Õò¿üù?þSöàÿ£’oü+õ¿þ3Gü2¯íÁÿG$ßøWë_üf¾°ÿ†Z_ú8ŸŽøX·ÿ¥ÿ†[_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉßðÊŸ·ý“á_­ÿñš?á•nú9&ÿ¿Zÿã5õ‡ü2ÒÿÑÄüpÿÂÅ¿øÕðËKÿGñÃÿ ÿãT{5÷Õò¿üù?þSöàÿ£’oü,5¿þ3Gü2§íÁÿG$ßøWëüf¾°ÿ†Z_ú8ŸŽøX·ÿ¥ÿ†[_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉßðÊŸ·ý“áa­ÿñš?á•nú9&ÿ¿Zÿã5õ‡ü2Ðÿ£Šøáÿ…‹ñª?á–‡ýWÇü,[ÿQíü×ÜTþëÿÀ“ÿá•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe¡ÿGñÃÿ ÿãTÃ-/ýOÇü,[ÿQíü×ÜTþëÿÀ“ÿá•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkêÆý—ÐH‘ÿÃD|oËäpnÄÓ/z—þmèâ~8ábßüjoæ¾àú¢þWÿ'Ã*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×Ö?ðËkÿGñÃÿ ÿãTŸðËCþŽ+ã‡þ-ÿƨöþkîª/åøòü2§íÁÿG$ßøWëüføeOÛƒþŽI¿ð¯ÖÿøÍ}aÿ ´¿ôq??ð±oþ5Gü2Ðÿ£Šøáÿ…‹ñª=¿šûƒê‹ù_þ|Ÿÿ «ûpÑÉ7þú×ÿ£þSöàÿ£’oü+õ¿þ3_XÿÃ-'ýGÇü,›ÿQÿ ¶¿ôq??ð±oþ5G·ó_p}Q+ÿÀ“¿á•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøeµÿ£‰øáÿ…‹ñª?á–×þŽ'ã‡þ-ÿƨöþkîª/åøòwü2§íÁÿG$ßøWëüføeOÛƒþŽI¿ð¯ÖÿøÍ}aÿ ´¿ôq??ð±oþ5Kÿ ¶¿ôq??ð±oþ5G·ó_p}Q+ÿÀ“¿á•nú9&ÿ¿Zÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe¥ÿ£‰øáÿ…‹ñª?á–—þŽ'ã‡þ-ÿƨöþkîª/åøòü2§íÁÿG$ßøXküføeOÛƒþŽI¿ð¯ÖÿøÍ}aÿ ´?èâ¾8ábßüjøe¡ÿGñÃÿ ÿãT{5÷Õ?ºÿð#äÿøeOÛƒþŽI¿ð¯ÖÿøÍðÊŸ·ý“á_­ÿñšúÃþhÑÅ|pÿÂÅ¿øÕ/ü2ÚÿÑÄüpÿÂÅ¿øÕßÍ}ÁõEü¯ÿ>Nÿ†Uý¸?èä›ÿ +ýkÿŒÑÿ ©ûpÑÉ7þúßÿ¯¬á–×þŽ'ã‡þ-ÿÆ©?á–‡ýWÇü,[ÿQíü×ÜTþëÿÀ“ÿá•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe¡ÿGñÃÿ ÿãT¿ðËkÿGñÃÿ ÿãT{5÷Õò¿üù?þWößÿ£‘oü+õ¿þ3Iÿ ©ûpÑÉ7þßÿ¯¬á–×þŽ'ã‡þ-ÿÆ©?á–—þŽ'ã‡þ-ÿƨöþkîªuÿàGÉÿðÊ¿·ý“á_­ÿñª?á•nú9&ÿ¿Zÿã5õ‡ü2Ðÿ£Šøáÿ…‹ñª?á–—þŽ'ã‡þ-ÿƨöþkîªuÿàGÉÿðÊŸ·ý“á_­ÿñš?á•?nú9&ÿ¿[ÿã5õ‡ü2Ðÿ£Šøáÿ…‹ñª?á–‡ýWÇü,[ÿQíü×ÜTþëÿÀ“ÿá•nú9&ÿ¿Zÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe¡ÿGñÃÿ ÿãTÃ-ú8¯ŽøX·ÿ£Ûù¯¸>©ý×ÿ'ÿÃ*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×ÖðËCþŽ+ã‡þ-ÿƨÿ†Z_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉÿðÊŸ·ý“á_­ÿñš?á•?nú9&ÿ¿[ÿã5õü2ÚÿÑÄüpÿÂÅ¿øÕ7þhÑÄüpÿÂÈÿñª=¿šûƒê‹ù_þ|¡ÿ ©ûpÑÉ7þßÿ£þWöàÿ£’oü+õ¯þ3_XÿÃ-¯ýOÇü,[ÿQÿ ¶¿ôq??ð±oþ5G·ó_p}Sû¯ÿ>Nÿ†Tý¸?èä›ÿ +ýoÿŒÑÿ ©ûpÑÉ7þúßÿ¯«ÿá–‡ýOÇü,ÿ¥ÿ†Zôq_?ð±oþ5G·ó_p}Q+ÿÀ“ÿá•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøeµÿ£‰øáÿ…‹ñªOøe¡ÿGñÃÿ ÿãT{5÷Õò¿üù?þSöàÿ£’oü+õ¿þ3Gü2¯íÁÿG$ßøWë_üf¾°ÿ†Zôq_?ð±oþ5Kÿ ¶¿ôq??ð±oþ5G·ó_p}Sû¯ÿ>Nÿ†Tý¸?èä›ÿ +ýoÿŒÑÿ «ûpÑÉ7þúßÿ¯¬?á–‡ýWÇü,[ÿRÿÃ-¯ýOÇü,[ÿQíü×ÜTþëÿÀ“¿á•?nú9&ÿ¿[ÿã4Ã*~ÜôrMÿ…~·ÿÆkëøe±ÿGñÃÿ ÿÆ©á–×þŽ'ã‡þ-ÿƨöþkîª/åøòwü2§íÁÿG$ßøWëüføeOÛƒþŽI¿ð¯ÖÿøÍ}aÿ ´?èâ¾8ábßüj—þmèâ~8ábßüjoæ¾àú¢þWÿ'Ã*þÜôrmÿ…~µÿƨÿ†Tý¸?èä›ÿ +ýoÿŒ×ÕÿðËCþŽ'ã‡þGÿRÿÃ-ú8¯ŽøX·ÿ£Ûù¯¸>©ý×ÿ'ÿÃ*þÜôrMÿ…~µÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×ÖðËKÿGñÃÿ ÿãT¿ðËkÿGñÃÿ ÿãT{5÷Õò¿üù;þWöàÿ£’oü+õ¯þ3Gü2§íÁÿG$ßøWëüf¾±ÿ†[_ú8ŸŽøX·ÿ£þmèâ~8ábßüjoæ¾àú¢þWÿ'Ã*~ÜôrMÿ…~·ÿÆhÿ†Tý¸?èä›ÿ +ýoÿŒ×Ö?ðËkÿGñÃÿ ÿãTŸðËCþŽ+ã‡þ-ÿƨöþkîª/åøòü2§íÁÿG$ßøWëüføe_ÛƒþŽI¿ð¯ÖÿøÕ}cÿ ¶¿ôq??ð±oþ5Gü2ÚÿÑÄüpÿÂÅ¿øÕßÍ}ÁõEü¯ÿ>Nÿ†Tý¸?èä›ÿ +ýoÿŒÑÿ ©ûpÑÉ7þúßÿ¯¬á–×þŽ'ã‡þ-ÿƨÿ†[_ú8ŸŽøX·ÿ£Ûù¯¸>¨¿•ÿàGÉ¿ðÊ¿·ý“áa­ñš_øe_ÛƒþŽI¿ð¯ÖÿøÕ}aÿ ´¿ôq??ð±oþ5Kÿ ¶¿ôq??ð±oþ5G·ó_p}Sû¯ÿ>Nÿ†Uý¸?èä›ÿ +ýoÿW“KíðoöŒø/ð÷âÆ{XÆ­¤]KŸˆµ ›i­¥¿òZ9m›³å8*T©R:äŠúÇâgÃ=kàþµðC\Ñ>7üTÕN«ãýG»°ñ‰äº¶žÚYZ5TÝŸ( *T° 漟öªÿ“àý›ÞÐ?ôï=iNnnÎÍ4úU¤©Æê馺ßsõjŠ(¯<ö‚Š( Ê_ÙSþOƒö’ÿ{_ÿÓ¼õ‡ì·÷hŸû*ž!þp×Éß²¯üŸí%þ÷ˆ?ôï}aû-ýßÚ'þʧˆ?ô(k²¿_Dy˜O³ë#êº(®WÆ^)Ó¼áOøÃVŠy4ÍÎ[éã¶PÒ4q©f + œ"¹mÙ”¤¢›{#ª¢¾ðQ‚Äÿ÷‹yÿ§(?øõié?ðP?€××qÛj+â=& »í84Ižìbw R+¥àq+þ]³ÏY¶ ¿âÇï>梱´MoGñ&“§ëº¥o¨h×Ñ,ö×–’ #ž6 ¬8 ÖÍrÚÛž‚jJè(¢ŠQ^gñcân‹ðƒÁ:—Æt±TkCÚS’qîvÔW+mã ê¼ñf‘¬Zj:´RÌ×¶$ñ‘%ðÊH$m=ëä_ +þÞ_ +¼k®h^ðç†|W6·­J-ìb–ÎY$e,¡›ÀÀëƒN +•q‹vßÈš¸ÊUI¤å·Ÿ§Þ}ÉE~'|7ý´><ë_<7¦j:„7öθ4«Gg-¬fBå¢Pñ¸³’@Í}ñoöøÖ¾øÿÇ^ ·øke‡îžÙnßRhÀT ’¡:ã5Õ,¶¼f ’m«ïþgŸO=ÂN›«&â“¶«Êý/Ðý1¢¼ú_MÃøö7 ¡ÿl}Ì;wyw—»3Æq_ üý¼5¯Šüàk†öz|:üÆ&¼RiL¹y2 ”Ç^õÏO V¬e(­#¹Û[C8S©+9í£×oó?I袼ƒã?Æ_ üðœ1ñm®¡q¦Ë{ŠÅ¦Ä²Hd“;N”cƒžk(FU$£vΚ•#JsvKvzýòÂoÛáÆ/Ãà_Øë–ZÔö²ÝBu[xâŽa *•‘²À㯨¯ïm´ËÍFöQ¬M4²·DE˜Ÿ ÕT¥:R娬ȣˆ¥‰‡´¥$×t^¢¾‹þ +!ðJw‰aÐüXÉ4«Kö(@mÎ7úî‡ ý+ïX¤Y¢ŽUû®¡†}§V…Jö‘µÈÃâèb¯ìf¥nÞd´QEdt…Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Ù”\"gæeoéV*eóÑOÞÚØý*z(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šøçâí³ð³á§‚>/øû]ð÷‹'Ѿø¡<'ªÅakjóOvË -Ã\*´_¾PK²6sòôÏøƒþ +Kð7ÃüûF_xOÇoàê×:=”6&ö)á2†ic7b0„Àø"F=2o¿kn?fÛëþËE§þб¯¾+ÿÁ,ÿeïoêÿúíEžý©¾xü|mIñ ⮑¨kZ/Ûmí×ìÐYª4«s¶fØäH¡Boç$S<ûU|=ñãüM#Fñ'â>‘«k:WÛm O³Ã§º$ës‰ŽÉ ‘vªîœ•¯ƒfõ?ðLÏûüWÿ¢`ª_³ü}Á??ì@ñßþŽ· ¥~ ÁGþürñ'Ä øSÂ~:³Ô<¡_ø‚ýõ{ã’ÞÑ•dHŒwnL„¸ÚU<äŠô_ÙcöÔø[û^Ÿ†ÚŠ´Ñ៳ ³â[[X<Ó?™³Ëòn%Î<¦Îí½±žqøÿñÿ’»ûXÿÙ2ñ?þŒŠ¾¹ÿ‚¨ý¢ë¦ÿ·tú}ñöÐø]ðÏÂ_¼e®è>)¸Òþë¶žÖ"°µ¶i®nnVF¶:‡Œ ”ÉrŒ0Ø5~þÛ_ +>'x#á?´ÅvÚGÄ¿„t¸5 [TžÅI\¼á.V°¿Ì¬ÍÓåôüÔý¬ÿä„ÁJÿì§h_ú'M¬oÙþM«öÿ²ßqÿ¤÷´úWàßÛÛàÿŽ¿hýOö_Ñü7ã(¼ay{e.¡ughºq{Tw‰å¤ÚB¤Ä 8Èíþ8ø÷àÿøËRð>³¦ëj¶^¿ñ¤’Ùà Dl¬Ø,±‚Ò)ó‰ ªެ+ðoörÿ”ÁxÃþÆÿé=Å~—~Òßòq>+ÿ³ñOþ”%} á_Ú¿áç‹ßÁ)¦hž$ˆø§À“|B³ûUµºˆôèš5h¥Û1ÅÁ2®nR3óŒW‹|)ÿ‚’|øÁào<3áOÛhÿ ô¸õmZJÆÉ'¹…üÝ«l©váŸ÷-샧>Ÿ1| ÿ¿Ù§þ̓RÿÑÖ•ùÇû É´ÁEìG´ÿÛÊýÃø­ÿø%ðâw…>ø›ÂÞ8¹ñˆ,ôëÛ[2ÆÊKtŽ÷Pv{¤`W?0 +qص}_ñ'⿇¾Éàñž£r|_â+O Ù>8ÜEup$(ÒïuÛ6YwÇ¿šŸÛ·þOOà‡ý€!IðÕííò5D•£2MÿÛàîÇðv¯>ðÇíåðƒÅŸ´¦©û,é¾ñ„tûÛ» u ›K5ÓüËdgr$&B¤!Áò²N2|£ȹ¡ÙØÜéc×Êÿå2^7ÿ±ŸÄ?úO=~²üý¿~|~øÍâÞðߌì¼[£G{$÷zÕœVŽ-¥X¤ØñÜÈä–`W(2:ã¥zÏÄ?ÚgÀŸ u_‹F»¥kÓÜü>ð”2ÔÚÂÞY쥒xÖ8 J¥¦ÙÉW¸+óqøSÿÎÿ”‡|Vÿ¯_ÿétU÷ígÿ#ÏíÓÿdNÿÒ½N€?Cüoñ¯Â¾øFÿu{ Zo +­¥ï٬ᮊ\¼IÒ*îÌËŸŸ‚xÏ›üEý¯>|2³øó}¯èž%ž/„£Jmil-­˜Ý}½Q¡û.éÔ>Ñ ß¼¦;nã>SûKÿÉ€\ØÿúUe_~׿òÿ‚¡ÿ×?ÿè«ZúÇKÿ‚—| Õ¾x—öжðŸWÁ:½‡®,¥°²Ïs$hêÈ‚ìÆcÀI6sÇzõ߆¿¶?Ã/Š>øâÏè>(·Ó¾*êú†£Ç¨[Z¤–³Ù¥ËÊ×An"k&Ò…É%rN?žÊ,~3ÙPÓÿôš +û×ö;ÿ’-ÿÍÿ±ûÅ?úK«PÝ¿ ?à¡¿¾*|bñwÁ/økÆ–þ+ðìz”·Wz’ZH,IÝ.Ø’§nPg¾+â_ü»àO¿|ø‹â/ +øî}â^q©é0iö/=¼Pº# •kµUbdÎ:äŠüˆýŒå ¿¿ëׯú2JòÿÛ+þMkþ ßÿbŽ«ÿ£íèú_ñ×í ேßí ­iÚľ u®©ö;HakÓÆÍ‹±åX÷0dy˜ë‚{øWÄ¿ø(?ÁŸ…tÙ÷Ä^ñ¤þ4Õ¤Ó¢‚óO³²{%kÝžV÷{¥q0nÂvÍy_íEÿ(½¸ÿ±?Dþvµù›ûhsÿ?øcÿ_¾þpPî®§ûX|9Ò­¼guq¤ø‰“Ã8²ðâÅonÅõ —RHÇÌÜ!fm®l!ã.ý¨¿jχŸ²_„x'©šñ„¿ðQŸ‚/¾-Øx_Ã>7·—áæ‡{â Qõ;$[‹kS‰ÜÇt土º(=ȯœf¯½ÿ¾ÿ±/Ä¿úL•ù×ûÈÉûzÙ4ñþ†hö?ãü»àGÁ+/†7þ*𯎮âñׇàñœºE…”­ ´¿ufßvdõ +X{××_¾/xoá·„ü5ãnÇS¸ÓuÍWJÒ-¢°Š7•gÔ."·„¸gP<ªX‚Hà1À?Ë÷üþE¿Ø»þÉF™ýk÷Óö°ÿ’ðwþÇ¿ÿéÒÒ€:ψ¶WÂÿ‡øáãoCñDúgÂÍrÛ@Ö"±µ¶inn'Ž ­ƒN¡ãá.Pä7`š? ?mŸ…?| ðÇâ‡ôÛèÞ;ñaðv›£ij“Cz#’Mó„¸uXvÄß2³6qòú~nþÕ_òC?à¥öRôý%Ó«›ýŒÿäÙ?bÿû/-ÿ¤—TúOàïÛïàïÿhíCöaÒ|5ã(|g{ya%ýÝ¢Xy–Êí&$[“&Ò# /“Œ^ßã/ÚÁÞøðßVÓ5©uËO_øÞIí!… 6’,rÆ¥V3’ãjíÚGVø1û=Ê_¼Uÿcˆ?ôDõúWûAÿÉÖxŸþÍóÄÿúU}á¿ÚóῊ/>YXh^&ŽOø"ãÇÖ qml«ŸÝñM‰É8®åõq^?ñgþ +Mð3àׄ~ x×ÄþñÕÖ™ñ+EþÝÒbÒì¬d–Þ#;nCÝ Y1*ð…Ç^}~Mø=ÿ!ÿÙþÍ·VÿÚùñûxÿÉÿ‚xÙ7_ýÖ€?t~4ÁC> ü ñ÷‚~x·Âþ6¼×|Qae¨ÙÍ£ÙYËqÝ9HÄ%Ò0`GÌXÄ×Õþ(è Û^²ÔnGŠüEgá›?ìèÑü»«þ[˹×,îa¸Ž0¦¿œø(ßü7ìÝíáÿèó_¸?µï1þÍöVü=ÿµèÛ¾|ZðçÄ«¿ˆÖš –¥ž ×çðæ ×ñF‚[˜QÚ®Û£Û*৯òÇÄø(¿Á/†ß +ϬªëÅ?hÆÙðã zxvüÿä¯k¯ ý¥›gìûñ¡ý<7~ò W5'j‘õ;««Ò’ò‘øKû?xkß~1øÁž- þÕ&–;¥IÌ……Üb@r9Qõé^û[ü8øcð‹â&‹áÏ…úÓÝZÜiíq¨iïv.Î.ðê7®NÒr1ž†¾gøqà½kâŸ|=ðïÃsÚA®kR<øóÄ^ñ†4m6×NÒÅòM§M3»·š8Æ0sÅ|9©~Ð:‡Å_اâ7‚µí/N´×< {¢ÅºTÞ «.Qb(pŽ¥J¶‚:‘]GübëÎøÝãÄôðâœÛÈ®:”á*UjN+žïô=*ªÃ‡¡JoÙò««oñ|ú¡ñ;þ +ñÀÞ>ñ÷„ôÿønæÇ@Õ&°Š{‹‹…’UF`žq]'Ç/Ûoâ7Âÿˆú¿ƒ´O +ørëN´²²¹I¯^q!3@²0;N0 =«ó/ö‹½)ñ¯ãšö%»øúW«þØ;>;øœgþaIçþ¼£®ˆaðíÃÜZ¯þD䫯¨U~Ñé$–Ú/M¼‘úÏñãߊ<'û2xãe†¥ÍâMBËM¹’Æv”[«\Þ6ãŒþ5ò—…¿h¿þÔþø½ð£ÄÞЬ­eð…õý¼ºcNòˆŠycqŒžÜçØük›oüÛÀ²úéZóоpÿ‚x•ðGÂ/‰2|/ñ§‚þ!ç&¡6†MÂYI1…&cÆ6ÜpJýɵøqð÷DýŸ§ñNàÇćÀÒ1Ôí,bŠ|É`wŸ0.ì¶Nyç½~'þÉvgˆ>>üÑõ«¯tÉõÒZÜ(d¥¼².áЀꭃÇŽµ7Ž1²ê»îtc0ÕÕJœù¥}¶Ö6ÓÈúŸFÿ‚üfµÖÿ[ðß…õ-ÌÌÚe¤3ÚÌ©ÜG3;|Àtܘ'®+Ýÿnïˆ:Žg/„~*ðôÌú?‰u›;ëc ÚÁ<™«Ì:ØŠù#þ + g§é´ìZuŒ©q ØÏ2[¢ ‘÷J»ˆàO°§üjÕ@ý‹ÿd &rdšþþ]¿ì¢NäXRŒisR«ÙßôeÎxgˆÃÕŸ2·_T¿SçÏ|@ºøqã¯øúÍÈ—AÔ"»uýdÛ2ŸPcfâ¿gm‹Öžýœî$Ð/ÔßxìC¦iòÆÜ´.ùdSè! ÿ}Wâsxöoƒ§âÔ%¤Ò¡ñ)ðåìÈ‹|HäÏ£1(sÜŠ·ã‹^)ø‡áƒþ¹ŠIÛÁv/£Ø rçPžiUbr;0M‘}2kzÐz°©ü¯_ÌäÃTž…Z?Γ_=áù”÷ËkldŒíHºôBÃòÅAi +üð'…õ=FÚMWÅZÝ¢6™¢[ȪÓá´’?!"]Ã-ƒ“À×àÇ?ÂÿxßáÓ\›‰ôh"ŠIÏW‘í–F#ÐbÒ½Sö¢ñ¥ÿ‹~+\7Ú +ǦhVŸf$B>Ê’¼’}h­båMËewù :¹u:Ê Þm/OˆúNø(WÇi¯Q·Ð|-’%(-ÍÔ‘ç®Ãp\ Øêϵ~‘þÍ¿ôÿÚÁ7:úiÙ~ Ó.>Ç©é¢O5"“hexßtn§ PzWãŽi›_|*ð‡ì÷ðëáÌzO‡´Ï³ƒ}¿PÔ®£—T‰I]òe‹Xôàf¿M?`¿ƒ/øaàøƒÇZlºf½â‹¨ç‹Iœâ[[XЬ~hس1^ `s\¸ÊtUò(Êúz†YW,O+ªêBÚ¶´OËú×±÷µ™´µãŸLQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW8ó“ûûN>™b«¶Ï´&Ömm¿N3V(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(ùüý­äÚo¿û-Ÿú*ƾ>ø­ÿ(±ý—ì|Ö?ô+Úûöµÿ“iý¾»Åè´ÿÑV5ñÿÅoùEì»ÿcæ±ÿ¡^Ðé¿ìÏþ§þ ™ÿbŠÿôLKöqÿŸø'çýˆ>:ÿÑÖõwögÿSÿÌÿ±Åú& +¥û8ÿÇ×üóþÄÿèëzüæÿ‚yÉ^ý¬ÿì™xŸÿFÃ_\Á?Ô~Ñ?õÓFÿÛºùþ åÿ%{ö³ÿ²eâý }sÿ;ÿÚ'þºhÿûu@þÖ_òAÿà¥_öS´/ý¦Ö?ì‡ÿ&Õû +Ùo¸ÿÒ{ÚØý¬ÿä„ÁJ¿ì§h_ú'M¬oÙþM¯öÿ²ßqÿ¤÷´æß³Ÿü¦ Åÿö0xÿIç¯ÒïÚ[þN'Åöoþ)ÿÑé_š_³—ü¦ Æö0xÿIî+ô·ö•ÿ“‰ñgý›ÿŠ¿ô|tóÿÀßøûýš?ìØ5/ýi_œ°ßü›?ü[þÄkOý¼¯ÑÏ¿ñ÷û4Ù°j_ú:Ò¿8ÿaÏù6ø(¯ýˆÖŸûy@Ÿ·wüžŸÁûxGù­~íþØ_ñõû+ÿÙ\пô]Õ~~Ýßòz_ÿìáæ+÷oöÂÿ¯Ù_þÊæ…ÿ¢î¨âýþEÍ þÎÂãÿK¾Pø'ÿ)’ñ¿ýŒþ!ÿÒyëêýþEÍ þÎÂãÿK¾Pø'ÿ)’ñ¿ýŒþ!ÿÒyè#þ ›ÿ)ø¯ÿ^ž!ÿÒè«ïÚÏþGŸÛ§þÈ6ÿ¥z|ÿÎÿ”‡|Vÿ¯_ÿétU÷ígÿ#ÏíÓÿdNÿÒ½N€=Ëö—ÿ“¸ÿ°‡ô¦Ê¾;ý¯äÿCÿ®~ ÿÑVµö'í3ÿ&qÿ`/ÿéM•|wû^ÿÈþ +‡ÿ\üÿ¢­hó»Á_ò‹ŒÿöTtÿý%‚¾õýŽÿä‹Á3¿ì~ñOþ“jÕðW‚¿å?ÿì¨éÿúK}ëûÿÉÿ‚gØýâŸý&Õ¨åØËþR ñûþ½ÿ×§Œÿôd•åÿ¶Wüšßü¿þÅ[ÿGÛPì¯íGÿ(»¹ÿ±?DþvÕù›ûhÊO¾ÿ×ï„?ö…~™~Ôò‹»ŸûôOçm_™¿¶ü¤ûáýxCùÁ@fø×þ@?´Wýœ¿‡¿ô§M«_ð[ù ? +¿ìp_ý"¹ª¾5Ïöíÿg/áßý)ÓjÏüÇþHŸûÿH®hOöjûßðKïûüKÿ¤É_°wüŒŸ·¯ý“Oÿèf¿E?f¯½ÿ¾ÿ±/Ä¿úL•ùÙûÿÈÉûzÙ4ñþ†h†ÿ‚ƒÈ·ûÿÙ)Ó?­~ú~Ö?òB~Ø÷àý:ZWà_üþE¿Ø·þÉN™ýk÷Óö°ÿ’ðwþÇ¿ÿéÒÒ€?>ÿj¿ù!ŸðR¿û)z?þ’éÕÍþÆòlŸ±ý—–ÿÒKªé?j¯ù!ŸðR¿û)z?þ’éÕÍþÆòlŸ±ý—–ÿÒKªòÿÙçþRýâ¿û¼Aÿ¢'¯Ò¯ÚþN³Äÿöož'ÿÒ¨«óWöyÿ”¿x¯þÆ/è‰ëô¯ö…ÿ“¬ñGý›ç‰ÿôª*ùÓàïüŒ?²'ý›n«ÿ´+óßöñÿ’ ÿðÿ²n¿ú ­~ƒüÿÿì‡ÿfÛªÿí +üøý¼ä‚ÁÙ·ýíÿNó×f§£<Ì_Úõ‰úµEWé…Q@”¿²§üŸí%þ÷ˆ?ôïo_X~Ëwö‰ÿ²©âç |Ÿû*Éð~Ò_ïkÿúw‚¾°ý–þçíÿeSÄ?ú5Ù_¯¢<Ì'Ùõ‘õUxGí8éìíñ®IR5ðΠY˜€ò©=+Ýê•핞§isa¨ZCscp†9mî:J„r¬¤`‚;䋳Lôgh¸÷?•O†?ÛáO<5ñAŸG»ÖtYX-oî“!hÙm­ž1Þ½Kã‡íQñö“¸ð凉Δ–ZT¯-މá¸ä˜¼î» ·,îv’ c'¹¯èŸþgÃ?ú'^ÿÁU¿ÿZz_ü¡ÏöÂZ=…ÎAó¬ìa…óõU½¦åÏɪóÊa;Œd¬ó0O¼ ŒœWͳ¯íwû>xÇVñæ‹¥éúÜŽ˜Ú|ö×w>B¨\8‚¤`©= NUã·ÿ~êºÃø‡QøKáKmäµìºT #?PÄíäç½Dq‰©*ŠéšO,qp•YÇ¿ÏüÙüÆüHñ]ÿˆu¿xÓÅž±ây¤×$_³¬©+î +9Ä@·F95÷íµðÓź‹´ŸŒvÚUΡðçŘWV±‰¦ŽÆXí‘ sl¢°•ˆÚrFr+÷ Søà]fhçÖ<¡ÞÏKIw§Ã)X—!PRB€xQÀ«ÚÝÞᯠj—÷¶ˆ4-.ÊI¤µ†àC*±Ï €ªúû¼\c·üò#û%rN3ïg{uWÿ3ùÇ×?jˆþ9øA¢ü¹›L›Áz6Ñ5Õ•«™äŠi3µ@*¼à+õ¯¡à›syŸ´¶3Ÿø¦nOþLA^;ñcö½ñÅÿj>“Áþд Ëáu,ú-£CqRÄHí_LÁ2<¬]x×Ç_ä´‘<5k§ Þ프¹¸yIñX×$p ×8ê¬ùhI5kœhóâéÚ\öëkh¿CÀ?à  Ÿƒÿi¯ÜË5½µ·ˆôû=^1$Š›ßi‚B<ó +“î}ëèø%ß…-5]oâ¿Äa¸¶´··Ñ!t!Æé šP>‹?ZýpÖ¼áÜCwâ é:ÔHcIµ8§dBs´R@Ï8«š7‡¼?á¸%µðö‰a¦[JûÞ>Ù W|¸…€k†X¾j>ÊÚ÷=jy&'Ûóivíê-Ÿô‡_>&øâòÞ£k—pG²ª‘ 9–3Œô)"ãŠý£ÿ‚zxa4_ÙnM|ì2x–÷PÔüÅ ‡&$9FØ…}³¨ü>ð¯{>¥«x/B¼Ôg Ëuu§Ã,’™—'€&º/MÒ¬ Ò´½>ÚÓK…|¸í-¢Xãyà ž€Q[íi¨[PÃeÿWªês_·•ÏåàV¥§?ÆŸƒǨ[<Ÿð—Yáe$æá±Æs^ÇûjøbÿôŸÅ;=n·‹_u; dEݼ±¨-<6ÖVSŽ„s_Ð…¿ÃO‡Vw÷–~ðôpH$ŠxtÈãprH\ƒžr*Oü;ð'Ä;(tÿø?H×ì¢;£‡V³ŽàF}Wp8ü+_¯/h§Ë¥¬aý“û—O›[ßð±ù%ðïöñ¸ñO†4¿‚^+𾛦Åqáký)µô½$Í4V. +>På0Ä’Ó}c ÒmÀÜÊN9Kec–©\('’Z!ŒQŒ/ÃþM~¡S-”åR\ÿ–Ú§ú–²»Œ¿±_Åïy‘;ëúµòÙ\«)]Ä‘˜\€¬Š¼æ¼óöaýŠþ3iŸ¼ âo‹~Ið–€ÍªHæþÞäOwÄ1„F'Û~HÇÈ+ö¯Gд_Yÿgè:Ež›c¼Éö{÷§j€2{œVÅfñs÷¹vfË/§jnZ¸þ'ósûx =3öø¯oswo·PÙÜ*Ë(BCÚŒOµXý¥>j–7ÂvvrÍàxGHêp‚ÑYßÇl‘ºÊË÷€ +¹8'#9â¿ ýWÀ^×odÔu¯蚆¡"…{«Ûf‘€³)$ÇZÖ:„tqáó¢Ù`ˆüŸìãnž@û¾^6íöÆ+XãyT}ÝŒg•óºËâwZlÿ¦~|ý»§øMà-+Ã'ᆵ-cK·[ø†ÞXìžáaZãK>0 óuë_£¿±gíâ/Ú'Á¾,Ôü[aî—ªàŸö xïÿG[ÕßÙŸýOü3þÄ?ÿè˜*Ÿìãÿ?ðOÏû|wÿ£­èó—þ ãÿ%{ö²?õL¼Oÿ£"¯®à‡Ÿê?h÷ôý»¯‘¿àž_òW¿k?û&^'ÿѰ××ðCÏø÷ý¢ë¦ÿ·tcö²ÿ’ÿ*ÿ²¡è6±¿d?ù6¯ØSþË}Ïþ“ÞVÏíeÿ$þ +Uÿe;BÿÑ:mcþÈòm?°Ÿý–ûý'½ 5ýœÿå0^/ÿ±ƒÄúO=~—~Òßòq^+ÿ³ñWþ”G_š?³—ü¦ Æö0xÿIî+ô»ö–ÿ“‰ñ_ý›ÿŠôzPÏÿ¿ãïöjÿ³`Ô¿ôu¥~r~Ã_òlÿðQ_û­?öò¿FþÿÇçìÓÿf¿©èëJüãý†ÿäÙÿࢾ¿ðƒÚíån~Ýßòz_ÿìáæ+÷oöÂÿ¯Ù_þÊæ…ÿ¢î«ð“öïÿ“Óøÿ`ÿ5¯Ý¯Û þ>¿eû+šþ‹º Œ4ù4/û; ý,zùCàŸü¦KÆÿö3ø‡ÿI篫ôù4/û; ý,zùCà—ü¦OÆßö3ø‡ÿDO@ÿðLïùHwÅoúõñþ—E_xþÖò<þÝ?öA´ïý+Ôëàïø&wü¤;â·ýzø‡ÿK¢¯¼¿k?ùnŸo€Úwþ•êtî?´Ïü˜Çý€¼;ÿ¥6Uñßí{ÿ ø*ýsð_þе¯±?iŒÃ\Ø Ã¿úSe_~×ßòÿ‚¡¹à¿ýk@þ +ÿ”Xügÿ²£§ÿé,÷§ìyÿ$[þ ›ÿc÷Šô›V¯‚üÿ(±øÏÿeGOÿÒX+ï_ØïþH·ü7þÇïÿé.­@+þÆ_ò_¿õéã?ý%yí•ÿ&·ÿïÿ±GVÿÑöÕê±—ü¤ã÷ýzxÏÿFI^_ûeÉ­ÿÁ;ÿìQÕ¿ô}µ~ÊþÔ_ò‹ÛûôOçk_™¿¶‡ü¤ûáý~øCÿhWéŸíEÿ(¼¸ÿ±?DþvÕù™ûgÿÊO¾ÿ×÷„?œöoäûEÿÙÌxwÿJtÚ±ÿ±ÿ’ð§þÇÿÒšƒÆ¿òý¢ÿìåü;ÿ¥:mXÿ‚ØÉøSÿc‚ÿéÍiþÍ_{þ }ÿb_‰ô™+ó¯öÿ‘“öõÿ²iâ?ý ×è§ìÕ÷¿à—ßö%ø—ÿI’¿:ÿ`Ñÿ'íëÿdÓÄú #þ + ÿ"çì[ÿd£LþµûçûXÉ ø;ÿcßÿôéi__ðPoùÿbÏû%gõ¯ß_ÚÃþHOÁßûüÿ§KJüûýªÿä†ÁJÿì¥èÿúK§W7ûÿɲ~Åÿö^[ÿI.«¤ýª¿ä†ÁJÿì¥èÿúK§W7ûÿɲ~Åÿö^[ÿI.¨ËÿgŸùK÷Šÿìbñþˆž¿J¿h?ù:ÏÿÙ¾xŸÿJ¢¯Í_ÙçþRýâ¿û¼Aÿ¢'¯Ò¯Ú ÆVx£þÍóÄÿúU|éð{þFÙþÍ·UÿÚùñûxÿÉÿ‚xÙ7_ýÖ¿AþÈÁû"ÿÙ¶ê¿ûB¿=ÿoù ŸðOû' ÿ ÚСÿÁFÿäéfßû<;ÿ£Í~à~×ÿêÿfû+~ÿÚõøÿÿ“¦ý›¿ìXðïþ5ûû^ÿ«ýš?ì­ø{ùO@_²/ü‡k¿û*š¯þ“ÛWáÏí_øa_€?öR?ìšEÿ [WíïÁOù;Iÿìˆx_ÿK.ëñ öüÿ’#ÿøÿ²iþm_·Ÿ?äìçÿ²!áý+» Ð:(¢€>Uý©>ïìíÿeSÃßÎjù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ÏÙßþʧ‡¿ô)«äÿÚ¯þOƒömÿ{@ÿÓ¼õÙ‡û>Œó1{KÖ'êÕQ\g¦QE~RþÊŸò|´—ûÚÿþ௬?e¿»ûDÿÙTñó†¾Oý•?äø?i/÷µÿý;Á_X~Ëwö‰ÿ²©âç vWëè3 ö}d}UEWé…Q@Q@Q@9âo€µêtP—k¾ x›W²ñˆ¾x3T׬Òíµ-K@³¸¸·H¿Õ*Hñ–PŸÂ{b»]WÞ׿²ÿ·4-;QþÌ»ŽúËíÖ±Íö;˜Á 4[Ù"† 8à œÜ¢€8Õø}àDDðF€±¦¨uÅUÓ u"roÛÅÆå¯ß÷¬›/„ ´ïÜ|@ÓþøJ×Ç“ÈóKâK}Ö;ù$p»\„ó 0['פQ@káσ¿¼â¯xGáo„tO\¬‰>±¤hv–—s,‡s‡š8ÃÌ žO&·5Ox\¸Ö®µ¯hz…αdºf¥-î ͨZ)b¶ó–Rd„b² “Ç&ºê(žÔ|+á}gAo +êÞÒï|0ÑÇ Ò.ìâ–ÔÆ„C ›Tª1´c¥cêß ~ëÑøš {ÀÔ¡ñˆk)¥[Î5aÄBè:8 áwîÛÛÜÑ@QÀ¿‚vþ¼ðe¿ÁïGàû»¥¾¸Ð£ðõ’ÙÏpg€G±¤ŒÞ·4†_ ô ?iÚÃï iº~3Üi6¶M¼ér¸*ò[* ³`Y$Zîè 2Ò> üðþ»}â áOƒ´ïÞ¬«u«ØhV]\ OïD“,aØ9'p$îïš­¬| ø'â7BѵÿƒÞÔô}·Ó,5YO^8#xÊĬTª8¯V¢€9}GÁžÖ<6<«xSG½ð‡–ÿaÝØÃ-Ÿ–„O!”¦Õ*ÀÀÅszÇÁŸƒþ!ñ¿Œuÿ…>Ôü]DÑkš†…i=äF/õeghË‚ŸÃƒòöÅzeÆÏð÷ÀWIÏü?,wÚ‚j÷I&™ ›ä*RæL¯Í2•R$9aƒÅ0ø}à?ˆv6šgü x›N¶˜\Ciâ 6 ø¡”doT•XÁ#p®ÊŠã4χ¾ч†ÓHð?‡ìG‡ ’ÛGzl*0;mª<•`*˜ŠÀÑ> |ðÔºäÞøGàÍ*mfÚ[-IôÝÎÜêòÒC9HÇ™’K+dÔõ*(Êuÿ¼Tº2x£à÷‚5tÑíRÇO]SÃÖW"ÂÝRwÆ|¸ÔŒ„\(ì+·Õ<5áÍrÆÏKÖt 6ÿL³š‹{KÛHæŠ b`Ñ:#ЀU€ÊÅoÑ@§ðÇá¶·eâ-3Zø}á­CN×îVïVµ½Òm¦‹TT*ËpŒ„JáU@g€ gŠf“ð³áަé:.ƒðçÃn¥_iØØXiÐCeyÏúD1¢ŽnOïç­wôPšéÿ¾é~,—Ǻ_ÂßÙøæIWñ®‡iû»ýö7*‚BÌ:’Ù=ë¢Ô<á WS›YÔü+£Þk2ØI¥Éuc³Ig!Kfr»Œ,@-;Oq]EÄÚ|9ø{c&—5—€ü;o.›§>‘dðivèlì[­b!>H1Œ)ÀȬsàÁoYèwˆþx'UÓô(~Ë¥Úê^²¸M‡ƒåÛ£ÆDI•Sµ8¯T¢€<Ë_ø3ðƒÅšŽ¬x§áOƒµ[OŽ8m/u] +ÒêkXã9DÞ2Ȫyé]–©áí]þÍÞ‡§êN»ŽúÌ^Û$ßd¹!&p;$PN`ŒðknŠÃÒ|9áíMVMAÓ´é5;·¾¾kXà7—/€óJT ò6]²N95Åjßþ kÚ5‡†õß„~ Ô|=c<·Vº]þg=µ´òd’8š2ªîI,ÀORkÔh +×þüñU®…§ø£á‚u‹ ³i–Ú¦gsŽxȉ2£å@Šìì¼+á;S:ÖŸáÍ.ÛYûzÛ­ìãŽo²ÆIŽ0.ï)K©£'º(¢Š(å_Ú“î~ÎÿöU<=ÿ¡M_'þÕò|³oûÚþ篬?jO¹û;ÿÙTð÷þ…5|ŸûUÉð~Í¿ïhúwž»0ý=æbþ׬OÕª(¢¸ÏL(¢Šü¥ý•?äø?i/÷µÿý;Á_X~Ëwö‰ÿ²©âç |Ÿû*Éð~Ò_ïxƒÿNöõõ‡ì·÷?hû*ž!ÿС®Êý}æa>Ϭª¨¢Šã=0¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(±U3£ÿVʬÕfUÆùä)\}H«4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEò§íK÷goû*žÿЦ¯”?j¿ù>Ù·ýíÿNó×Õÿµ/Ýý¿ìªxÿBš¾Pýª¿äø?fß÷´ý;Ï]˜³èÏ3ö½b~­QEÆzaEPå/ì©ÿ'ÁûI½¯ÿéÞ +ú¿ö[û¿´Oý•OèP×ʲ§üŸí%þö¿ÿ§x+êÿÙoîþÑ?öUçìïÿeSÃßúÕòíWÿ'Áû6ÿ½áÿý;ÜWfìú3ÌÅý¯XŸ«TQEqž˜QEùKû*Éð~Ò_ïkÿúw‚¾¯ý–þïíÿeSÄú5ò‡ì©ÿ'ÁûI½¯ÿéÞ +ú¿ö[û¿´Oý•OèP×e~¾ˆó0ŸgÖGÕtQEqž˜QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEY—£ú+ÌŠ³U™OÚ#lð†>¤Uš(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠùWö¤û¿³·ý•O9«äÿÚ¯þOƒömÿ{@ÿÓ¼õõ‡íI÷goû*žþsWÉÿµ_üŸìÛþöÿ§yë³ö}æbö—¬OÕª(¢¸ÏL(¢Šü¥ý•?äø?i/÷¼Aÿ§{zúÃö[û¿´Oý•Oÿ8käÿÙSþOƒö’ÿ{Äúw·¯¬?e¿»ûDÿÙTñó†»+õôG™„û>²>ª¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +Ì[όӟ®F*ÍVfýüi˜©9ôä:³@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@*þÔŸsöwÿ²©áïý +jù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzìÃýŸFy˜½¥ëõjŠ(®3Ó +(¢€?)eOù>ÚKýíÿNðWÕÿ²ßOÚ'þʧˆœò‡ì©ÿ'ÁûI½¯ÿéÞ +ú¿ö[éûDÿÙTñó‚»+õôG™„û>²>«¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +ìGœƒø°N=²*ÅWez?p¤gñb€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€>Uý©>ïìíÿeSÃßÎjù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzìÃýŸFy˜½¥ëõjŠ(®3Ó +(¢€?)eOù>ÚKýíÿNðWÕÿ²ßÝý¢ìªxƒÿB†¾Pý•?äø?i/÷¼Aÿ§{zúÃö[ûŸ´Gý•OÿèP×e~¾ˆó0ŸgÖGÕTQEqž˜QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEY”ý¢6ÏXcêEYªÌ[όӟ®F*ÍQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|«ûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzúÃö¤ûŸ³¿ý•OèSWÉÿµ_üŸìÛþöÿ§yë³ö}æbö—¬OÕª(¢¸ÏL(¢Šü¦ý•ä÷ÿi÷¼Aÿ§x+êÿÙoî~ÑöUUý©>ïìíÿeSÃßÎjù?öªÿ“àý›ÞÐ?ôï=}aûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzìÃýŸFy˜¿µëõjŠ(®3Ó +(¢€?)e_ù>ÚKëâý;Á_X~Ëwö‰ÿ²©âç |Ÿû*Éð~Ò_ïkÿúw‚¾¯ý–þïíÿeSÄú5Ù_¯¢<Ì'Ùõ‘õ]Q\g¦QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWer?ñ#Û«Y”ý¢6ÏXcêEY Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( •?jNŸ³·ý•O9ëåÚ¯þOƒömÿ{@ÿÓ¼õõ‡íI÷goû*žþsWÉÿµ_üŸìÛþöÿ§yë³ö}æbö—¬OÕª(¢¸ÏL(¢Šü¥ý•?äø?i/÷¼Aÿ§{zúÃö[û¿´Oý•Oÿ8käÿÙSþOƒö’ÿ{Äúw·¯¬?e¿»ûDÿÙTñó†»+õôG™„û>²>ª¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +Ì[όӟ®F*ÍVfýüi˜©9ôä:³@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@*þÔŸsöwÿ²©áïý +jù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzìÃýŸFy˜½¥ëõjŠ(®3Ó +(¢€?)eOù>ÚKýíÿNðWÕÿ²ßOÚ'þʧˆœò‡ì©ÿ'ÁûI½¯ÿéÞ +ú¿ö[éûDÿÙTñó‚»+õôG™„û>²>«¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +ìGœƒø°N=²*ÅWez?p¤gñb€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€>Uý©>ïìíÿeSÃßÎjù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzìÃýŸFy˜½¥ëõjŠ(®3Ó +(¢€?)eOù>ÚKýíÿNðWÕÿ²ßÝý¢ìªxƒÿB†¾Pý•?äø?i/÷¼Aÿ§{zúÃö[û¿´Oý•Oÿ8k²¿_Dy˜O³ë#êª(¢¸ÏL(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š¬Ê~Ñg€¬1õ"¬Õf-çÆ?ƒiÏ×#f€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€>Uý©>ïìíÿeSÃßÎjù?ö«ÿ“àý›ÞÐ?ôï=}aûR}ÏÙßþʧ‡¿ô)«äÿÚ¯þOƒömÿ{@ÿÓ¼õÙ‡û>Œó1{KÖ'êÕQ\g¦QE~RþÊ¿ò|´—ûÚÿþ௫ÿe¾Ÿ´Oý•Oÿ8+åÙSþOƒö’ÿ{Äúw·¯«ÿe¿»ûDÿÙTñþ… vWëè3 ö}d}WEWé…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@Q@™¿cæ*N}9άÕv#Ï1ÉRsøŽ*ÅQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|«ûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzú¿ö¥û¿³·ý•OÿèSWʵ_üŸìÛþöÿ§yë³ö}æbö—¬OÕª(¢¸ÏL(¢Šü¥ý•?äø?i/÷µÿý;Á_X~Ësöˆÿ²©âý +ù?öTÿ“àý¤¿Þ×ÿôï}_û-ýßÚ'þʧˆ?ô(k²¿_Dy˜O³ë#êº(¢¸ÏL(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š€àH‡üEOUŽï>?îm9úäUš(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠùWö¤ûŸ³¿ý•OèSWÉÿµ_üŸìÛþöÿ§yëêÿÚ—îþÎßöUϬªè¢Šã=0¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(³g팛‘ß9Õš¬ÀyèÄüÁHÇâ9«4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEò¯íI÷?gû*žÿЦ¯“ÿj¯ù>Ù·ýíÿNó×Öµ'Üýÿìªx{ÿBš¾Oýªÿäø?fß÷¼?ÿ§{ŠìÃôôg™‹û^±?V¨¢Šã=0¢Š(ò—öTÿ“àý¤¿ÞñþíëëÙoî~ÑöU²>ª¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +Ä´#碑\‘ÏéVj»ßÄù謻}rWŸÃb€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€>Uý©>çìïÿeSÃßúÕòíWÿ'Áû6ÿ½ éÞzúÃö¤û¿³·ý•O9«äÿÚ¯þOƒömÿ{Ãÿúw¸®Ì?Ùôg™‹Ú^±?V¨¢Šã=0¢Š(ò—öTÿ“àý¤¿Þ×ÿôï}_û-ýßÚ'þʧˆ?ô(kåÙWþOö‘ÿ{Äúw‚¾®ý–ú~Ñ?öUϬªè¢Šã=0¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(³¾xÚF=yf«Æá?(VõäsVh¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(å_Ú“îþÎßöU<=ü毓ÿj¯ù>Ù·ýíÿNó×Öµ'Üýÿìªx{ÿBš¾Oýªÿäø?fß÷´ý;Ï]˜³èÏ3ö½b~­QEÆzaEPå/ì©ÿ'ÁûI½âý;Û×Ö²ßÝý¢ìªx‡ùÃ_'þÊ¿ò|´—ûÚÿþ௫ÿe¿»ûDÿÙTñþ… vWëè3 ö}d}WEWé…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@Q@Þ|g?.ÆüEOUØ7Ú³ò…lʬPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPÊ¿µ'Üýÿìªx{ÿBš¾Oýªÿäø?fß÷´ý;Ï_X~ÔŸsöwÿ²©áïý +jù?ö«ÿ“àý›ÞÐ?ôï=vaþÏ£<Ì^Òõ‰úµEWé…Q@”¿²§üŸí%þö¿ÿ§x+êÿÙoîþÑ?öU²>ª¢Š+ŒôŠ( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š(  +Ä7Ú‚6ml~*ÍVù¾Ð˜û›N~¼b¬ÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPÊ¿µ'Üýÿìªx{ÿBš¾Oýª¿äø?fß÷´ý;Ï_WþÔ¿wövÿ²©áÿý +jùCö«ÿ“àý›ÞÐ?ôï=vaþÏ£<Ì_Úõ‰úµEWé…Q@”¿²§üŸí%þö¿ÿ§x+ëÙoî~ÑöUϬªè¢Šã=0¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¹Ýö„#ýYSŸ®EXªÍ»í õa[?\ƒVh¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(å_Ú“îþÎßöU<=ü毓ÿj¯ù>Ù·ýíÿNó×Õÿµ/Ýý¿ìªxÿBš¾Pýª¿äø?fß®ÿ§yë³ÓÑžf/ízÄýZ¢Š+ŒôŠ( Ê_ÙSþOƒö’ÿ{_ÿÓ¼õì·÷hŸû*ž ÿС¯”?eOù>ÚKýïéÞÞ¾°ý–þçíÿeSÄ?ú5Ù_¯¢<Ì'Ùõ‘õUQ\g¦QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVmßh@¿êö±?^1Vj»ûDk”«dþUb€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€ +(¢€>Uý©>çìïÿeSÃßúÕòíUÿ'Áû6ÿ½ éÞzúÃö¤ûŸ³¿ý•OèSWÉÿµ_üŸìÛþöÿ§yë³ÓÑžf/ízÄýZ¢Š+ŒôŠ( Ê_ÙSþOƒö’ÿ{Äúw·¯¬?e¿»ûDÿÙTñó†¾Ný•äø?i/÷¼Aÿ§x+ëÙoîþÑ?öUœŠ±UË=T¶}9UŠ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠùWö¤û¿³·ý•O9«äÿÚ¯þOƒömÿ{@ÿÓ¼õõ‡íI÷goû*žþsWÉÿµ_üŸìÛþöÿ§yë³ö}æbö—¬OÕª(¢¸ÏL(¢Šü¥ý•?äø?i/÷µÿý;Á_WþËwö‰ÿ²©âý +ùCöTÿ“àý¤¿Þ×ÿôï}aû-ýÏÚ#þʧˆô(k²¿_Dy˜O³ë#êª(¢¸ÏL(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š¬ÌLñ¦?„œúr8üjÍVr|ø×Tœúr8«4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEò¯íI÷goû*žþsWÉÿµ_üŸìÛþöÿ§yëëÚ“îþÎßöU<=ü毓ÿj¯ù>Ù·ýíÿNó×f§£<Ì_Úõ‰úµEWé…Q@”¿²§üŸí%þö¿ÿ§x+ëÙoîþÑ?öUÚKýíÿNðWÖ²ßÝý¢ìªx‡ùÃ]•úú#ÌÂ}ŸYUQEÆzaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPbÇí›y*Çw§ Uš®ßëQvœ`Øàr8üjÅQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|«ûR}ßÙÛþʧ‡¿œÕòíWÿ'Áû6ÿ½ éÞzúÃö¤ûŸ³¿ý•OèSWÉÿµ_üŸìÛþ÷‡ÿôïq]˜³èÏ3ö½b~­QEÆzaEPãÁߊ^øIûaþÐÞ#øƒ¯dè×7ší”WfžãtͪFá6ÄŽÃ+œ‘Ž:äŠõ½s]ÿ‚mx[Ö|E¬ßgU»šúòàÇçO+—wØ€(Ëp WÔÚ÷ì}û:xŸ\Ö|I®|;ûNµªÝË}ysý¯¨ÇçO+—‘¶¬áFY‰À€YßðIJ÷ýü­jü‘]޵)>kÉ;t<¸á«Å8Ú ]½n÷>Zó?à˜ßóÙ¿ï¿RyŸðLoùìß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_xþ­[ù)ýÌùgÌÿ‚cÿÏvÿ¾üMG™ÿÇÿžíÿ}øš¾¦ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ-yŸðLoùìß÷߉©<Ïø&?ü÷oûïÄÕõ7ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùkÌÿ‚cÏfÿ¾üMIæÁ1ÿç»ß~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>oüþ{7ý÷âj_3þ ÿ=›þûñ5}Kÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Yó?à˜ÿóÝ¿ï¿RùŸðLoùìß÷߉«ê_øbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}Z·òSû™òÏ™ÿÇÿžíÿ}øš7þ ÿ=›þûñ5}Mÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«ÖþJs>Yó?à˜ßóÙ¿ï¿QæÁ1ÿç»ß~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>oüþ{7ý÷âj<ßø&7üöoûïÄÕõ7ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùkÌÿ‚cÏfÿ¾üMG™ÿÆÿžÍÿ}øš¾¥ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ-yŸðLoùìß÷߉¨ó?à˜ßóÙ¿ï¿WÔ¿ðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3åŸ3þ ÿ=›þûñ5güþ{7ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|³æÿÁ1¿ç³ß~&£Ìÿ‚cÿÏvÿ¾üM_SÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–|Ïø&7üöoûïÄÔyŸðLùîß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}Z·òSû™ò×™ÿÆÿžÍÿ}øš“Íÿ‚cÏfÿ¾üM_SÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–|Ïø&7üöoûïÄÔ¾güþ{7ý÷âjú—þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|³¿þ ÿ=Ûþûñ7øÒùŸðLoùìß÷߉«ê_øbÙ{þ‰þVµ?þH£þö^ÿ¢aÿ•­Oÿ’(ö´ÿš_x}Z·òSû™ò×™ÿÆÿžÍÿ}øš3þ ÿ=›þûñ5}Kÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Zó?à˜ßóÙ¿ï¿RyŸðLoùìß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}Z·òSû™òÏ™ÿÇÿžíÿ}øš3þ ÿ=›þûñ5}Mÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Yóà˜ßóÙ¿ï¿QæÁ1ÿç»ß~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>güþ{·ý÷âj_3þ ÿ=›þûñ5}Kÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Yó?à˜ÿóÝ¿ï¿QæÁ1ÿç»ß~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>oüþ{7ý÷âj<Ïø&7üöoûïÄÕõ7ü0ÿì½ÿDÃÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùgÌÿ‚cÏfÿ¾üMKæÁ1¿ç³ß~&¯©áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË^güþ{7ý÷âj<Ïø&7üöoûïÄÕõ/ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùgÌÿ‚cÏfÿ¾üMG™ÿÇÿžíÿ}øš¾¦ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ,ùŸðLùîß÷߉¨ó?à˜ßóÙ¿ï¿WÔßðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðú½oä§÷3åŸ7þ ÿ=›þûñ5oüþ{7ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|³æÿÁ1¿ç³ß~&¥ó?à˜ßóÙ¿ï¿WÔ¿ðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3åŸ3þ ÿ=›þûñ5güþ{·ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|µæÁ1¿ç³ß~&£Ìÿ‚cÏfÿ¾üM_RÿÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–¼Ïø&7üöoûïÄÔžgüþ{·ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|³æÿÁ1¿ç³ß~&¥ó?à˜ßóÙ¿ï¿WÔ¿ðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3åŸ7þ ÿ=›þûñ5güþ{·ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|³æÁ1¿ç³ß~&¥ó?à˜ßóÙ¿ï¿WÔ¿ðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3åŸ3þ ÿ=Ûþûñ5/™ÿÆÿžÍÿ}øš¾¥ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ-yŸðLoùìß÷߉©<ßø&7üöoûïÄÕõ7ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùkÌÿ‚cÏfÿ¾üMG™ÿÆÿžÍÿ}øš¾¥ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ,ùŸðLoùìß÷߉¨ó?à˜ÿóÝ¿ï¿WÔßðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3åŸ3þ ÿ=›þûñ5güþ{7ý÷âjú›þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞW­ü”þæ|µæÁ1¿ç³ß~&£Ìÿ‚cÏfÿ¾üM_RÿÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–|ßø&7üöoûïÄÔyŸðLùîß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}Z·òSû™òÏ™ÿÆÿžÍÿ}øš3þ ÿ=›þûñ5}Mÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Yó?à˜ÿóÝ¿ï¿RùŸðLoùìß÷߉«ê_øaÿÙ{þ‰‡þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}^·òSû™ò×™ÿÆÿžÍÿ}øš“üþ{·ý÷âoñ¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>güþ{·ý÷âj<Ïø&?ü÷oûïÄÕõ7ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùgÍÿ‚cÏfÿ¾üMKæÁ1¿ç³ß~&¯©áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË>oüþ{7ý÷âj_3þ ÿ=›þûñ5}Kÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Yóà˜ßóÙ¿ï¿QæÁ1ÿç»ß~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË^güþ{7ý÷âj<Ïø&7üöoûïÄÕõ/ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùgÌÿ‚cÿÏvÿ¾üMG™ÿÆÿžÍÇû~&¯©¿áˆ?eïú&?ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgË^güþ{7ý÷âj<Ïø&7üöoûïÄÕõ/ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼>­[ù)ýÌùkÌÿ‚cÏfÿ¾üMG™ÿÆÿžÍÿ}øš¾¥ÿ† ý—¿è˜ÿåkSÿäŠ?áˆ?eïú&?ùZÔÿù"kOù¥÷‡Õ«%?¹Ÿ,ùŸðLoùìß÷߉¨óà˜ßóÙ¿ï¿WÔßðIJ÷ýü­jü‘Gü1ì½ÿDÇÿ+ZŸÿ$Qíiÿ4¾ðúµoä§÷3å¯3þ ÿ=›þûñ5güþ{7ý÷âjú—þƒö^ÿ¢cÿ•­Oÿ’(ÿ† ý—¿è˜ÿåkSÿäŠ=­?æ—ÞV­ü”þæ|µæÁ1¿ç³ß~&£Ìÿ‚cÏfÿ¾üM_RÿÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–|Ïø&7üöoûïÄÔyŸðLùîß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}Z·òSû™ò×™ÿÆÿžÍÿ}øš“Ìÿ‚cÿÏvÿ¾üM_SÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÇõzßÉOîgË>güþ{·ý÷âj<Ïø&?üöoûïĵõ7ü1ì½ÿDÇÿ+ZŸÿ$Qÿ Aû/Ñ1ÿÊÖ§ÿÉ{ZÍ/¼_V­ü”þæ|³æÁ1ÿç»ß~&£Íÿ‚cÏfÿ¾üM_SÃ~ËßôLòµ©ÿòEðIJ÷ýü­jü‘Gµ§üÒûÃêÕ¿’ŸÜÏ–|Ïø&?ü÷oûïÄÔyŸðLoùìß÷߉«êoøbÙ{þ‰þVµ?þH£þƒö^ÿ¢cÿ•­Oÿ’(ö´ÿš_x}^·òSû™ò×™ÿÆÿžÍÿ}øš3þ ÿ=›þûñ5}Kÿ Aû/Ñ1ÿÊÖ§ÿÉÃ~ËßôLòµ©ÿòEÖŸóKï«VþJs>Zó?à˜ßóÙ¿ï¿QæÁ1¿ç³ß~&¯©á‡ÿeïú&ùZÔÿù"øbÙ{þ‰þVµ?þH£ÚÓþi}áõjßÉOîgÍZ&¹ÿÚðæ·£x‹F¾6úΓw õÁ#“Éž'±ÁS†à‚pEy/Æ/Š^ø¹ûaþÏ^#ø}¯khÖך”·Ö{}“.©#”Û*#,ˆr9ëkîÿøbÙ{þ‰þVµ?þH­= ö=ýœü1®hÞ$Ðþý›ZÒ®¡¾³¹þ×Ôdòg‰ÃÆÛZr§  à‚phU©Eó^MÛ¨K ^IFÐJééu±ôÝQ\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䃽î0‚ud'?³Fèqp$Ž'0NÇ •ªG„Læn™"Ž-Yu-ä3‘Säv_Æ÷¸òÎÙÑ»Ÿs—p7‡ðÆ)B%š ÚÔèªÂM²Ø­I¼‹Ë!yþßK@8|\‡ÑÞà”FV4 ¨«ª©­m’ùGU«¡·ú¥XdÁöÍÒ¼Ìg=3§ô²q‰ÍŽðs”àCvå÷ 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" !´ +82h­ŠSKœÀ©å&¨Ÿq¨C½½RG´õ“¶X‡O*íG)*9ù×Þç„¡µ½÷ûïóßç¿ÏsOØã»ÖÖZ{íµö~Ïa„ #õ®hk]zÄþÍ,¨ù-„q«[¯lû÷ÁËÂN„œ —¯ºj™ù¦3¹¹V!T½¢³cm·%®EhÁ»Ç’+[;—¯¾á„®{gW[çŸg|êF¨ñ„lo_Ùº¡Sú„_‡ÐÊ€×Vu,iímÙ ý­ì„r× +€}£#Zy ÊÙË:—_9(­x ¡+d„„+–¬ëÖ¾›ôðGuv#$]Z?·¨ÄÖ|ùn„Öø–Wvoøæ>WBŠ¡|ѹ dGñ}w‘¥ò¯²šÏC¨Ì éË|çß~{fØŠdÀ* >ù™Í½ 1GñQ&ÄÉ'²@‰C˜WX}A +ŒêÎb—¡)Zÿeª^DVbù4:f¾œå3h;|5ËXýÅ,ŸMÇÌÏfù|:¾™å  £ƒÌÆÏw°|²/Fgc²þ–gsáïMÑÓDžàLÑÓ„çKu)š@½Ü›‚‘Q±aF +FF^ÃÕ)ZG +†C†—R0cô§` ÞX›‚QÐvãOS0 +šjÊMÁ@½éÊ œ¦¿¥`x¤š )þòÈov¥øË#·¹(Å_ÙÍSSü\óÂyä5_‘jŸÖßâ/mgWŠ¿<Ê4ò—¼•â/ÒÍHñ—ÂŒ¤ø }Y2Rü…v,E)þòH³LOñ—Ö·§øKëoHñÆly8Å_ÚÎ`Š¿´þÍa̖ߥø ã±|“â/ÀXM)þBûÖPŠ¿4QŠ¿4¿0Å_Àµ®A PêBkQ;ê@«‘k´tÞXˆKІm]kÛ;Vke…Åc K6ük`ô8Ô”@¹ƒÜ<´ 54“Áv ntêd5S Ôy·B}²µBxRVÁŸ†æ@ÝrÀh© RÚó:ˆ—¢Âǵ’ââqÚ¼mÚÌŽÕÝWu¶iS:º:;ºZ»a¸…ZõªUÚœöå+º×jsÚÖ¶u­k[Zø¿oxÕÕSªkæF¼ [¥M†FVÁÙÐñÙúê®öÖUçËiÒ¶[!t³q,…¶¯„´ ]uhÙÿ‹9ÐVW³“xó¡Ô%:j Í…\++%{^ µE¬µ½‚ÍQCK ÔO»Ùh)t!›ˆÖ¾VkÕº»Z—¶]ÙÚu…Ö±ìŸrHk_­uóù«Û»Û–js»[»ÛyõÒ¢Ž.­žtiK:zVwwµ·­-ü?—,£\ŸÜ±j)šuËáY[ª½lè/`Ä´ŸZVßÍmåžç^á^€xÿY™Ålg˜—@ºÒuð”Öô ‹ÙlÖB_üè<ûêFë×&Ûø,7Ñrë¹5@=m½-NÑ㪠zìàü$~"?…Ççuþ"¾Ž¯Ž?9ò:Ú& 5ulŽ0ŽlC¿çBÀŸK Ý¥Œ‡Îå‚~þîÙèè›]5Æå« íaóIr+) ËØ¨»Y]ÄKY}'Ì1‰‘ä™5]ŒÓÝ)n­f\M–[YKŒ—W²Œò1kcT6VÁ³% ktIŒÑqtKû£óîŒî³R¶ž{ Äß?‡d™Â.Þz˜„/eêçï)A1V±\.ÀçAJ×ÂâÔ¸¿¿íÕÿ s?×úRÖÒr¨ëb’ž”ù%gWÔ÷ÍàüUvá¸&žÇ#:“ä\ºY£k•¶ŸœëR¨YÏfÞÁÖÿ¿’„Ö ¸Þ–Z³¿r)U»®‡aÒÑ®Kqy´ +¹ + þ• 6uôhW¶^¥õ¬måÊpYÇên­»C[Ú¾¶s<ý¦uvµCåxÒiëZ­³­ëÊönª_Åtâªö%m«ið€¶ÑÅj;»:–ö,馊sýŠö%+ÎëÒöÕKVõ,…MNDÇêUWi¹íyZÛ•‹¡íó WÿËÞøÒöÕ˵®¶µ —Pu}®ƒ¤âNµ5‘Í(·zén»’êö®vèuiÇúÕ«:Z—^H„ÖäÔA³ŸUñ=Ý=ÝÚÒ¶u0e +³¢mUç…*ü¿Ëÿÿ.ÿÿ3–ÿÕìøg¶ÑÙ|ù{¸ L£FÄ„j¼SªÏd3¦òº–QÝ(otl’Ƶa&œïh¹3Û—tu¬íXÖ÷ÿÕ¾‡óÎæjÖŸ•ÅzÀYv–sˆZæ̨¡=ÍJ­º¦Ö¢1P×À¨˜”Ýv&-s!îa²˜§c«@ãQÉYNÍ£QMWëzª#ë—-£ª«@›Ó±4ã,PÌ«Z׎ÑZ©îloÕæ¶ö¬^ +TÓÆVŒ/ùÿÏt¾=çø|ÁÓ íÖ󟜳aϯMÚ³çפlÛóë.°s¿OT¾oíÿ0‚´…Ï:jA^8– mäïƒ?g/Ÿÿ4i;Ÿ_s¡}þ“ﳩÿÙŒ.èå{míó1ç}/Íkÿaçìñ kÏÚæçןµÓ/¤í¨Íþ?Ç‹:§3JqD0;Dè鎄&?MpB”H•î@Ÿà*ñ Œ|²($÷#DZy£ÖS•Õ³¬'+gW¢*È[Ï@4¶8h Úr ˆGg4nèŒ. ï€¾Cô\ñŠÄl²BxYQ­nεì刬`¤X‘]~gÑãNˆ¹SW•ÿ4Þ¯ñÅ<áÈÝý¶G¯ =6>yÜzUUY+­ÐnÆ¡0)³:Æ•—ârÚ=nÒöâ½½KÜ8´mùEe¡ÄìÏð_¾ÀALŽ>ŸøubáŸI%cï~þ.ïüžtYôƒ¡%–û§ñS¼S|Sür¶7Û—íçÜa~¿Õû@Úéd<‘þD†lGÖ -clƺŒ3vf¼Ÿ!gP¾¸®X±-TÔ•õÐy°Ÿ`£e/ÐCc‘‘)ïŒ{‚rÄíÆõ0dÀrĺžø2Gx’q°²r&ÕbÃÑ5Ç@G›×TÚì°’K£ÍS~Ð8ˆ2F†últ }–èfk/[+Ù©­"Ê>MûE2e^£nPÒ|i$ÍAÏã4ÿš›¨dÔÍn|¥Eé2FŽŽ?¾ ¯MmÁqvÐ ãÊbáKθìÒ7¬JIäE‰7ž‰X{¿ú·è„¶¦Ærâs–_ýðôÅ3K§.vc!ñÝXùÍþª…ó/o[ùÃôÏùåÏ—ô/®>Ù¦\š k% ¸”‡>ÔK¶¸Þp‘¦ß’Nöp {¹CÂAçGÞ}²Û‰äþ‘‡Uâ±ÇáLV£:€³uc½ ë¦&b2a÷&º%à(r%¯cOšÚxÁ+Èȧªù=SÜ8<0º­G6vvö^£Ò‘úlœíºxÖã#È—v1L-'@[EQsŠ!4¢Å5Ç1%fEФ”ª@T jv䰵Ũ'•»Ï’q)…•;‘"ÊÊž‰­¦®Ù ×wÍWèÚÐ8}Ú2Cb8íÊ—¯zëÚåïlÜ•øãÛ¯%¾Å7W¬¾±så5®O¹ö…3—¶Œ¹i÷e7®ÚúâÚ´çnz1qâSXO@\¾èª"úD¯0j¦ +Åè3FsWo›°È»ù>×4Ít™i¯éÓ«&E“$¨“„ŒF“iÿ\÷s¼“%JŒ¼‰3^E’n2†Â³8É`¢<}ñ<  Üø´°6<Ê»UÚ-½ q’ßRE6B|æCø<­êckÀ\˜ k›.ì*0S†›+) 팆öŠ-Ba”¿ÖúŠÅbÝãEÆ™Æ_?6 +()´@Þ(Ø2e¸ÔVê +Ù° “Ñk¾:x0q"±GNqŸ¹üo‰I&þk‚ÞH.‰ËÊxP=¯O\iè‘·È»|{…½òãæ'ƒæƒ¶çC¶·&—0ÎVc½Ú}€¼m=씞EoÑ S,yíÖ4 –•¦L¦´=S X$A*rÁ=U +Ö•ÃʈÂ)¸¾Æ ‚A=+À êLþ\×úÌ#õFlôçxØ}Ù§µO&—üÉæS [IýMɃR’ÇB˜ Ò¸Ò; PçV&vž7‘·$N¨ó¦4ýÐÚþ@ü»Äé·þ=ñ{œÿ§½¿~pãìY+:çÍîäçfÎkè¾&qòÝß%Nà&¼ ÿ/}öÌÛîºú–7m‚Uº¤É+ìÝüæAÄÕÇZl1Õà7Làǫӄ†' ÿfø•áCƒ4`'¡€¡È@Š U†zg 36¢›3~êB0/ÉF¥¿HÂ`ɶèfRÏaÎo3Ö˜¢B%Õ{ ÃLé[3µ‡Gç…Û%â Úíå ¹ןº'þ,• ÿ­'1#áx “ ôþoîÈyðÛ‹²Q16(–3±ðÀÈi}d^³½æø@ø@â{¬ëœ7Z¹0Ê7ŽCµèãj~‰ {´k}dKd—éï#¦Ç½û÷dîìóxñ ÿ™LÏzÇÍŽ›["ü.àã.j+Þ¹¨Bó9\!zUa}!)”¡UŽóDˆ;/§]¹äÓw~ýÙÊ–«7%†?xý¦Ÿ¬\TßвhÖìÿú¦…]ÝMËÛ8Oáƒ-¼ÿþ#Ëvç}î‡o&Ú¯9²þ5<{Þ勿Õ/j¾¨ûúk×-¿ö6j+Uwœ©ÕxXoœh«³µ®–·É Ë{Ì{Ð wÀ<`{Úñ ôKÛÃs,04™Ùæ8Z¢OXï¾×ó±õ§°Â“‹3V‹SO.LÁÔ`aR"[Ùâ,Vp½ò‰r"µ8{“‹ó¼ý!-¹>MÞ#õvl÷ç$שñ¼õyò¬]õOÖç(SŠ¿,-Rƒ¥Ih(+Œ-]Œ®ÍتΛºðjÛÊÝ?û+¿úg&Þÿú©wÉå×ΙµÖgž›9·¡÷̱áýO°-±7Ñ“Xxà.}ëÝ?¼õ¶›6߀­ô÷|˜yˆ…z7‹âx^Uöq„ˆa¬ ÅöÉ¿z’ùhÔ¬<‚Rü+‘d§$ÉœLˆÄ)@/ ÇÓótÆ|‰ø;ºE÷é†C‹ë4l6^Ã$ת¬¤UØaÊܹ1¥„-Þ!zžÃ–oÏÙå †(%˜ä©T‰­ºçU [ +éäBI9¢&ÂQ]©µ¤Œ =£P©aæÝ0ÇOaP›Êä͆26±‹ü…1y.DçæJ8ãk¹›@íôÊ}ò1N|…{KþHæ4®HŽqåzùn·ÜËí“ãÜ ²!i¦•–ň^ÊÌ´£º©¨$F4IÎ2¨Ù¥+Á™ƒ®ÍÔ ‘L$ÉK84†D¤‰¤TšEtéd¤8Iš4“L•”Þ$’ÏÉgÒ7Ä!¹Ò iƒ´UzŠˆTOtEG?hTš“½ƒ°Ýƒ5Òˆ‰†÷ƒpï|[Ë=w¦†î/wÖ= Ü· 4´^Ï…Aç —»XÀË…÷b·å˜Ìf”f¥Zׂd÷?ì(î@FqFKFgÆæ !Ãj9_§_¸©œÝSRê÷ܾ…M3eš„B>»fÊ2¹ ÿ›çl|bñ®Y+ßxñ¡}ë¦\>­¬W8ä~¼oË@»Í5üÿR¢¥pquà +}˜ZtÏÁ|\(ˆNë×WX¦[J+ +ôd¸7tÐ|DQEYT=²[g®5×Z$٪؜f§Åigg¹ØÒc¾ÊúŽjØ lð­ËتlõÝœ!*n§b´˜çš{Ì7šï4?lÌšÉè4™Œ£Ëäqç8¬NÜâìu§iAJ. œ ÉfjLGÉj"¦wÓ"½b\‹¼¸¥3„µPqˆ„‚®ó©–5vÉ9ªÑ¸9e 2¥pnãbÒ’ßl¾Öú +¶¥¬?°FÖ4S‚–¸Sþ¦Çä +I(d³£*yÿñÞæ—^l¹veâ§ïwÍ»|YåoÞ[YY?-ûéÏ„Cõ¿¼þÑÒÇßüdâ÷¸êɦàðܬìÆÉ3.3 +t7œþä_Àšƒë Ú2æ¾:†“Í&›ËmÚr»Å ¦îÜMê|óü¬¦Ð +ã2ûò`{îò1ë3nθ;h´‡èN•ˆÑToóùc³³f‡^Ìz1įÉZº.ëºÐï²~£j¾);+;TaŠ…êÔ:SMÖ”ÐJS[è*ÓÕYÛLÛ³ö¨{Me9U1‰YbȧúLî,)+¤šxìYàÕ}Z¬Ã‹;¼»½Ä{ˆ´¡4X}FE §894ÝM÷k1z§Ñ€[ðNÜ‹ãxËøO¼î¯°ò˜/ÈW¼_x°Gwxbž:)ö"½Ö8XPuøk[’¾‚·S2_7·q?ÒÇ71[\YH£]Ô€\=Ù=–L»¢Ç@Ë'—,³ð³€i“€‡SéúY@H ôFŸ–ë{…I³W¨,XhÝçºÙu¦ +ÕKƒ£"zþgô`Ú5A`*Ë*:N7Mɪ íQÏRÙ9_ÒÄ;{Œae±qãJ5>iÐJàN{Ü<“,jíÎÀš÷–·_tIlðO-[6}ý8vb”8â¸öÚë¦ãoõÜ:‚^H|™xœ~ûÖ«fǦ§Ù '.¸êç//ûË/Mk–”eUÄrŠ–]ùü-{ÆT¾Æ€Ndþp—*RŠùb¡AéOs§"‰X 9ƒ tU”À7E›è*‚¢37N²™ì$<ñÉÃO¥¸2»q?®T&ÏD!¿ôXJ'U2«‹tR;’˜Éÿ(1‹éôéï&Ñû*ða²aT>´]/É’"YA‰(Ë+ÒBeõnë.Û=®ûÝ{­Ï¸?p}*ž &£Œq)Ç¡ šé-jLÀ–˜¥§5¤µ¤qi›Óˆ–VœÖ›6”Ƨa° 5_±oÈÇù¨"ðŸ·2›<¹ûUÒ3…ÊÔ]ëGÐ,q³¥ ºÞj&¡,jŒ—ýç;®Ù¸Ùs‹¯;òó·?ÜèÌåÿÇçÇ_zåò»ÎEÏ$§?º»©õþùOQªKI·PÛèö(5C©G"6èþ 1Œ¯~H¹óÒ>_ìÞŸé +=;ðAd-!ZèªnrgÄx " Ì=ÑèG.%å(ÒêgÆ¿)ߨ3 +¯ o¨¯?Bï‚uù¾ñKô©¢<É?,<©>j|–ïžU_ç•B>K(R5ãýü…ûÕ»Œrê”@Æf“Hw$s0i (ã0H‡ü@Òn|@wQ+r)-Da‰g^ãüy–"SªiO¿dàm`¤¸_Cq`¤Dÿ‡ŒâÑ0r‚ª¢ ”T§Á *¢nŠâ”e…7)“:áŒàšðFNP ’"‹²$ ¦N—°5€üí8€‹uUŸ7<¯Q[ŠF´ì3ž¥ø}3‡›ýÞáa¿o¸Ù;zœ’´­©?6zøg«HðSræùä…IÒbäš”õ@£5Íôð¬G¤ã¶ÄC¸ècl½ˆ‡ó$^Wöc%÷õp`ÀšœöÝ,¼é#Ÿó…ü$B%x¾BòËéB†Û?#mZúôœßX?±)ã|µ¾…áe¾åá›Ãwø~ìßãL{ÍÿzšQM.·èsGÄÀKF“:†ÊîËìòÆšK¦–Z ä÷…÷ÝÙ´í™ÄÀ­7]’^î“kkû¶^ú£ºô-}öÔ‰‰·í%ÞHåÄ%áòì6 ù†;™e™Ž~2ˆì#§õ±†Šò´‹Óˆ}¸@]à^àmJÿ›$–ñMeiSù:ScjÚÒ½Šj4ƒø#?0¡Oœ”ƒÁ‚TOPöwfâLká–œ§q'¢ç³¾Œª$½×TÎ<>\ùÇY`q&íÍãTc·¦7OiÔ ËÄeê2÷2o{ºÐ v2ó±éýNòà8âr€J={¿û®ï{)‘¼l¿nM¿ªù†—·Ý,>qgâ³Ä7‰‰.kz€ä?Zß¹ûɃþ„êÒù0÷*X >ô;}v£¥ÉÞä^ai··»¯õ^åÛEv_µ¾êýÀú¾÷ ñ ù ǮӢc¼c¼k†}†»ÖÛdl7Jìåîr/·^XoÙ"ÜlÙæ{̾×=h?èVÌLBÓb4=`wÆÌ¥&ZãËŒ±Ôb‹™a©@3»Í€tE:À¡Ò §‡@}ñðHóH˜Öâ *2ÑŒ)X”?M +:}þÆês÷dÍ3GOÒïæcÑäÙ¤I‹aÍè Yò¤½\ųwdüØÄ˜—Ô·_»éІe.쌞üÕ‰ÿÀîã/}J¾*™;ïö'žಎ¢{ ‡1^jÎ^ê‘ÌÚµ¦äf§^`o›Ô&{RZîÑ8­(™›3É.fœàŠùfp5Ʈ߽Šâdâb R£› ’ÙB¿¸éÉ3›Â˜JŠÅ‚ü;¨ìe_FcåÙ®9•”¶$Oì™ ²bjÛÕv{RZÄæ¦`°,5A{i‰|«óE…oM|W½ÿÒgß%^ê»û†íE5W·n½qùÒ-\Ö„#`Oš±ïNb=ÓùÄ%«}ä™w³Û™ÏùÈŠ¥ã‡‘ÖI­¡â^å>ÓÝÖÇ„½ê³Ê³¦¿,;ñ4r±X«Ög>f:(ô¿¦¾n|_=b<-ýÍdJ·¤»tÐ.Ýl‹Y\/¸Þrq.& ™U,5{ %?ÒÁ‰±7˜[ÌÄìµS»÷ /-†Kíìø4CK£få%ÓhA2õ¦³T·€:í¥ßz¤_(]d·™ûyƒÝKÉmP¹’BT”¹(³#sw&Ÿi ʺÉ‚§´aô‚óÔã`öêN¯žë¬òꙈ@{©®fVkÕ03‹í0€°ÓÁ=¥ªiÚ7 +z2µ‰1ìtÐ}šÄûu+V«Ø6×tŒjÐfÖ½Y*™i§fÚ½Yb% +Ø%ç°µ–2{ ´¦"®‰EeqAf}9’ö±‡|‹½ã¾Ø—ø›Ú±óãØ.ëÜõ­“/pü ²ã9E÷=xàöA¢‰×Ï_{Ë4¼êêMS¦¬¥zà ààY¹Ñ€^2ŽÇù¼fÕlMüf¯ ó/x‰Ëm#N»ÛfvXÕìÀÈJœŠl1àE†1PF¨"¶YÜxÄÝ´˜i…vO@Ӣé*¥Ur½Ü sr®µÈ¶ÈFl˜×MfG˜8¡^÷›Ð·ç*ƘÛçÙ0HÚ“ïEA¥Ò÷Ï4ƒÉœ|õ­Š:¡ª ª(±À'µ9J™ÏP⑘VpÑ7‚¶÷Š{{6¬ O™tQÙÛo'>{€7Ü|ãÜìW¬³ë>>ó 7­ýÄl¾…YEx–¾x}Æ– b7š:ÇÞlÚ<–×0xË\1.%¥œŽ§)Üe–&gS΂¼Àª+,§m§ö‰¦R÷ÄÜÒ1à&ºërkÆœ0{ÔÛ`Ï6M†|£)bv{\RÞlº°ÀÝlcBÒo0&ÓÜüäå$Ó±±äBP\ilã_$P…°DhbV (Á .Éëóó a¿—*ÅçóûwŒÅcA è**ÍÚ}ÅgµÏÉ”þ±·ݬ†O¦NtF÷ÄÇ:ïæ0ñe¯R¯Ÿ¾S!ÉÖÑ-n Ó[–vg{Îò¼eÑö"‘îrÁíÝ÷ËDvMØSx  +ç_H_…«åŒÜ«Ës¦Cï_»ã~±K“:ŸÝ‘øËïÏÜвü¶­+Ún¨ŒweÝcC—ßÿÔïaöÿì®3?wheåàmfrÃã?yð§öþˆuxuM ×ݨOZpWPFZ'ãɶÇß`EÜB6i´­° ‡ÓfwpN‚-”¨œ¤¨ªÓ¥º2¨aYѵìØ>(Xñ³7öÜYÙ±Þ^/éôžð’¯½Ø‹œa·‹©-€íuá.ìòyª’„;u-¹S©RÒ‹ú8ÐÔÃÌ+¹2õ52‰ D9ƶ;‘fñ“[Ÿo} >#ñ™6û¢ÚÕ¥‰ÏÀ,øt÷´Î­;†o'c÷^ZV³íæá¯`Ò Ûì}Gv‡ ¡õƒH¡·6µJW²Y‰+CÊaåkE(-Ê&¥*N”Às°‹é쮀CÍ`‰‚(ñ*‘`Ïd²ÌŽñ>95¯só¨bË“]oXSFbWtô"úÇÉ‹hþ æg¾›Á‡¿û8´ 8´Fh@ÿIOG?î7ÙØÙ¯~­¯ &qVÎ!F”eâ>õõuåMõ#U˵pÄ$y•Zq¡¼N*ŸðÇù3ü_Ea–4K^&^ËßÊßÏ? Ü'Þ'Ý'«Þ.Fù¨/æKùr‘©Ž¯T°IU‘UAU8‘7¼H_Ç6dIåTÕÀ+u¿P$W$,µ™ˆ!Œ7#@ôM–ª¦Ll:oŸõÔ/¬(ê ˜%ÏÑåk­¯È•率_ïS‚©«eêý ®æäM ux‚X²mÃ><_š¸ ß”øuâ¯7€³s +¯K\3|9þx[â©Ñ·W7ç²!=òRhÈf!. ‡…¯“×@›„^¨ýilVŒF¹†|ü?p-ŧÔË®©[Ÿ‰÷€VŒà‰ƒ(°›¡/Ø…Œ.ÑmŒq19æ…jÈTyª·&dÔ¸¢¼¹JKÞæ¼Ýyˆ{¥=Æâc<ïpÞÑ<3Ê+Êk€/ä}’'æÑ—¡« ¼™=¤ /ù3è¶Ñ§JA¶{ð’Õf‹¤¥§‡#*ˆžÅ¶ÛôKËZl¸i€ÔêZ8#ê:ÒqK:N‡º§sÂᵸúŠ0#D©¢©>ÆЈ^ ¡Bv$Ñ'\+мù$ÂY"Èæ‡"Z¤82á#¾Ü?TŽ:Q©£¬¤®¬<û=lI§Ö4G+Ï-]æƒ +=ï>¼+J·%u]Ô?ò0/ÉãfK9rv)Ÿ[Õ1wËв»‹kúAÏC¹°¶3"³'®(L|–Y5®zEAâ3>|ûãóæÏŸ·è5÷ 7‘E?-¬œvËÝ Bjï¿tLí÷ŸIÞÔñMÀ37Ú­{%‡Çq©¼Bæx ܲÖÈ5–/¬‚ÈT›M2›D£Á¦*Áa7bª áú®ê?Qmª!l4SúšLƳΈOÀ.w¡†c”ú%—\£Vnð•ƈŠŽoJ|–=»bzw…pË;Í÷ÕHæSmãnìKøðOOYqã©^›öë}0Sx;»ôiŸãÏä¿9þæâ_#Ÿ Äî| +i².p,p7yw‘{Ä{ä]Æå=òá·Ê{ÆÏ„ÏÄÏMÖ½ò›ä¿‰/˯…y›x£ÌÙ˜<”DN^rVHþ–´Î4’f¢ Ü“¤“—4ÚGw?¥Ýº löv/éÖ‡›1;L %ß çœ·ÏÍÙ>üÀŸq,ñÆWw$þ¶kw¯^}×]«WßM²nÅâöÄk_ÿ9ñò#ýô±Çzxì1:ß[«ø]0_+ø'÷é…ãÓÄã*LŽXZ 7Ý4ÝQ“öMšB}ÜQ¿å”ôMš ëç|Öm0X-æQÖ–g6[ÂV+sT ïÑÎ<^ Œ´ûŸ–íMt¿§>íy~ +}çÃE%=õ6T„º*çf} K¾r“Ä™ÁÆõÀb÷mË_ó’å[µ KÿžNœJ|X;ø n°ÿÉŸôï}ˆú*—ÁÜÃÜm(ýD/·W’˜)æ¬LŸAjL5ÎérggÈ.O¬IhRš8š×]ùOg=2mÆ;<·zïÍFôÝz0sëéXÀ?§§T6äìÈ!9º7#–ãîêaßiƒ‹Çà¢1xLf°„«QjoJ¾C¤V%wfzKì‹n $?û ;»KéPönÝ‰Ž£Ô…J™ˆ±ˆÝ8œ5.Xœ‡›ô]ªæ5ô­ª®d1IŒÔ­Uê%Oø4'¿‡‘=ò†®ìU–\ˆ€_4UÆ +ší3Ò{°/÷*˜óŽé%øèû¡ô{‘p$;’z÷‚ -úýzTZŒýöÕK®,Ïqº¦'žºlãGŸ~ônnâo¶EÅZz¿ØÔxòë‡qQtÎüÜô"Íå´ÕMZpïöçn»eì¤Éw(Ó•¾lFÝÍw¼‡UùœÜ.üvÅ_éyçUͳL0Ï07Y$Ÿ y9· yì'ö؉{9ER%£—’Û‚<½ž¸‡kdÈÃyÀIïsaºiô#ý^b·n6”"µŸ¼´uãs½\ØcŸïªrîvîsr-ÎÍÎÎÃÎN9­NÍYìä>ÿ†ÞQsª.^zb"{§Ò92D/ÅÎ$ïĬ'™œ}Ÿ@ÑïZ”¦|üf ½“ÑÔ#¦.›l¡²Ò²¹zÈIÌð.¾æ’«+ Êu×a?>š˜w}4=í£üÒÙSÇÞ…ß:úÎ#‰m@Ÿ–™Ë‡ÁBz@÷,´-·Ý-pŠè+I¥­ŽÔÙ>#óýl¼ÁT—Ó©*¢Ãv¹Uf7³“’ÿÂNRä³’ŒOÈXþç.`r“ù;û¨9yàÓë5ç¹›6nÖ„çÛ¯xâì Ì©šÖ•}»ç/¾ü‰»IoÂ{´mb}Ï1<NÌÓ–à¥0ONÓ]B®¿(&ÑH¤‘L#p±ŽôCÊÜ9Í?!vEΠ˪Ñ>+±s~ůf¡Ãk#¬íº;C‹©H08‘σò 14Á°)©1›Œ¬-ƒâ‰ñ)XD*ª¢oDU¤n¸t»©¼AUB°y¥‚žëÞôܘÁ0›toòxüVµJ­7l€ëžTø*¾žçøC¤LÔͺÅX†°*„Ã>ã+ [>*\QïÌãͰS5ûØE+'¿cö + C`K;J¿Y‚“oíâ ÃC/*à‚=“˜‡#¯Oðˆfë/q0Ôþý©î‚’™¤©Ñx ©‘Dô±@Y‰D•% ¹I&oü’SÉTmF#»È *¸ +q7M¼‡»Gdççú†1 jß iÍ Pû\2é{Z::º °]„ÚET¾ôrnJıíµg°eÿ›Ø•x2ñ—gž›Fhøî#òäð|2#¬Ü¶rïÑoÉ•^çÉ=Ò þ-~O:adÉÏ{Å\±—§á&| î‘Ô0ŽJãð©Ïî1œOKJ–òÕ?AÂÏR_æåKÔy|“º”¿RÝ€¯Uïäï–©ïñ¿UϨ&Ž—$Eu󟯖òUj-¯¸xŸ:A¥^¡îåŸáßPOñгí·{©¾8Ò¶6OÍ—Ñü*ñ”‰ÈH‘é |GæÄFØ+¢Gu‹;;Æ…‰â$DDƒ!õø„Ó¬îdž0œ ¢ €­*+Š äÊ>±T¡ç0¹­Þ´ÛtÔÄ™8ZMJ ´Ú~"y-üònÛ9M°ÆK8}3­Í§X%µ+DôåÆèšÑûèdnôLÓS‘ôª2M'˜<°æ5kº0J1ã+¦\5âM‰ÛñÂç^Å3÷àm‰½G>"!Â%~‹³Êð¯ñôÄ3Tw˜³ù9ÀUŽ=mϰƒNÝk´Äd·É“h$ÒHpC¡‹+:YEÞd0‹V‚"ï £ŽÆŒ— ;=#Î6 ³BÙ9áH.ÊËŽ)(,*[R+Wþ?îûÏgÿ¼ÍÕ&î)´ýÙ§¡‡tî©~ÉT¢@jw²´Ï-‚Ì„RV_pgÉæç¸'Ñ"T +ÕOöͧÕOöë5%,-˜L‹Æ²´ON>–œ%j? A È’ÊÕCØa7„ ˆ0 'Ñ'F pÜcÜC}µháQhÈRíäé¿a£æò(ú:UÃèîWŒ´û‡V÷0`Y ¶BØ a„· ¨âÝFØOîîæ‚g÷â쳬Õ*÷S´ áîCL ÿ!îž~+£Í½ýG‰^måîB Šs3ÑÍÞh·#àu}c ëúUs‰àoAß¡¿†Û 1fe…¿\3Úü }Ãûa_q,™é·zK€ +æÚ¸Õ(„ÜFHA¹%f@º˜[ + ŒŽSï·XK6CU^Ź`M¸jÎJ ­á芣`=}æd?=}¹ù%0ã)œ—X8ŠA*sR_I@{–Óñ·ö+:¾­}VWÉóÜMœË8Àm(OÀò<§gU6“yýŠ©dgµ‘›Óœd À1Py5khu4Tmã¦ré°¢Ü\¨ƒWËe²t/÷ ,÷÷“þpz`èYîÇ ëÚ(t?))Z“úMæ’¡j…£¯ñŹۀ·±Îwö‡Ç— ê0—‹Š! ñ&ÈmbB¿rÛkÛSÛSÛaPÛAú· žl˜"îjÔÉ­G;!ì†<+Wte²sK9çÂXŸRb¨õ÷+f:2oŸÝÁÀ¼ýFsIÕóÜZóµÐ¦Îu÷{¼%Ïrùl*cú½i¡³ÄõyΓd º)KžçÒ”0\fŸ+¯@™ +raòKr˜‰¼CÞ£ì&oA™¦o¦Ò_¥ÒÿžLG†Èáä¢ oÓôhu:ù”Þr’ÑnÈò,yn€|Dè(ȇdUAzÊK!„´ÒC}Á×d ûý}&7,y¹/Z”ÊrROZ*cw—Tç—È‹ ãäH³!}‘ ¡,H_ ôÛÅ2DºÑë eh"¤O§ÒWÈsTÄÉ3ä ìÒßg¦Cˆ÷I4Ù×'Òäç}(Yj( +=ÁrUä&T@!l‚°Âu`}í$WCø!„k \Ëjº!ô@ ¿7Þ €Ñ  £0:£0:F'ë½ÅhŒÀhŒ†Ñ-€Ñ- ƒŽ·0ZF`4F`40ŒÀhŒÀh` €Ñ C 0tÀІ:`耡3 0tÀÐF1`F1`3ŒbÀ(ŒbÀ(fÅ€Q Å C 04ÀІ`h€¡1 04ÀІ0¬€a +ð†0¬€aeVÆŸã(`Œ£€q”aŒ£€q0Ž2Œ£€q0Ž’õû¹ÃÕ¿”ÀrP3”ÀrPÊa†rPÊáÔÔ»1ˆÍF› l†@q‡wp‡wˆá1ñê@q「8`ÄF0‌8ÈF0â £0z£0zF/`ôF/`ô2Œ^&¸=(Æ](ÿˬ!×áFöZ²ç±túŠ¥Ñ–^‹ö³ô´‡¥?D׳ôjTÎÒõ(ÌRh¥Ý( ã¾@¹¥Ú * Â"vCØá˽á#¤LÏâ-R½´[Ú'½ û¤£±ˆõânqŸø‚(ìŠD«N#&¦GAµ ,Þñ×`¸ŠåªH úž-ƒ¿‰é¶ãÚ×ùø­|üB>Þ—wäãj…\Œy¦é4TN`à¸Q7†'Ž@(G&fºíàWž@_x\`?—Lòô(¤_AØa„ë!”C(P!B€Õå|£ž•jò9Aí¹Á°Ev›¬ÞÓÿ ¢ßï‹äÞ³}‘bHú"õ<ÓY¨VðA¡V>œ{Ò}}cðøgÉ䩾À³<ÖˆAÒÜ)„䲾ȯÕ&<xÌþ†d:æMÓ9}6»/I´/¦ÐùÐQ<ÍÃè¤9)¬ìdO¡¾ÀDH²úZFÊx°ø Øð4åúa@_âFë†ÀñÀ_úaA<>ÔxHÞÊ¡?1¤ž+ø)WúªU +ûÃþT§éÀžœmû¡-œs0po 0p[Á€ Õ?‚qoc]ô®×È“º#°9Pè.8X˜h Ì 4ç@}_àçè0Qn$O 4@ƒÓa9}‹sØkWô@$P¡=G鋯'Û-/xŽR•${ôÍÏ 2>¿|Ûô|é„´SºLš,M”BR–”)eHNÙ.[e³l”UY–E™—‰Œd'õ£ÔñqŠôu2$ò4æYbˆ©c‡éïèÉÜ®¸ƒ«#us'ãºøÐT·X‹ŸšÀêìKãBh2ŽÛëPݼÉññѺidN¼Ðâ\¸eñ’4mm‹÷„ÚjâC5Úþ±/Ïã—é㱡šýèå©ó÷¿¬·ÕôÕÇN µÖ4õWU6V_Ð×¶³}5V~Oc•´±FÚWUõ÷<®¦«h_Õ´¯jÚW•^ÅúšÚN御q¿Œ&Ó£–öƒ +2Ü’lšì¶vN¢=81èݘvˆGø1dˆ6Å¡Éqú¨ º š>‚uF™¡Ú’zäÝ81˜v?–zd…j[hòÙÃNDèõo]<8÷ÒF**q½õûy¶–~Øc/šÚ^ÿ ÜÍü‰Ö~ï§ûû>===kiÔ]‹P]<n]|½Œ–$誥¦ ê +Gë8ŽÕíW”©#Cð0 +ƒÀÝ´;š‹bú:¯®‚×%‘^±W"ÔUèî÷g”t<;ø&àÇ‘õ}EÌ}&ëû³r¨ÿÒÝ_T–LÁ]¥iŸ?XBÏãÊ•¦9ÉT·@fgÎ΂å½9½½åôTýਠì¡[i_ÑuG׎²ÝM(ù–1ô÷`_z븗f¢Ñ¦èZvL†þžÔgO´£ç»6ÕêZÖ|÷(C’õkS'’½÷Œ¢õ¤ØÃ†”l$Y:û@)©žÁ3ú ªD;ý‡¬¨ÍGHü–¼„¦ÀÇÁŽøR|ÄGÈ¥ä~n5ïç÷ +ÕÂnqµd“&IßÉû”K”U‹:×p­ñã1ÓÝæW-6ËuÖ‡lÛ#ö_8.rNq¾ìÚïë¾ÞãõÌòæzâsù~ìwúoNÏKÌ(Èø  þª=“4)1 M±¢o¿ýöjë¹ÃµÑÏx½zž^5é¢Ê‰*Æ——ÅJKÆŒ‰æçåFÂ9Ù¡¬ ÈÌHOóû¼ô×a·Y-f“Ñ *2ýÖ>ö˜©¡Ú-n‰óáдi´j…ŠÖó*ZâTÕ^×Z˜v!¤ËþROBêg!±U«D•c´©!-þ«š6€/ š:þ£šP“?Îò3Y~'Ë›  ‚6Õ»¢F‹ãmj¼vÝŠíS[j ¹ýuJhJ›Z0íW 5@.î uîÇžI˜eˆgê„ýÉ&Tܪ™÷…jèâ\ÎÔÖ¥ñ†ÙSkÒ‚Á¦‚1q¯1ŒW¥…šZkÒ÷;Ñö9WõûtÍwá“‚1û­¶$a÷›-©ŒÑt~¦íì3–cà4W7ç,e1Qh:D\[¢ÁHC0§ñ4j¶/`ði€_ +i+SZ¶['ÐzŠr¬!mû_Á†l ÿêšÖT˜cý+¢Y*'gE žæãÑhû­üht'ï7;;ûÎ<óÞfv÷dwô·'…‘±Ó–^Ô¨ #šF= ‰™‘FùîüæÖoÝú¿òT„b¾ÃTáj®hö¶:”‘î)w—{ÜU¾ ]lX¯IV‡ +‡ëßU—Ég&%f†óÃî·c“%…w© jCxÚÏò¿Qh…Û½¨U$s1—ù¤;U!}[’,|x[YÈ¥Á.FÛÄ…ÃGnW¢2òÓssï‡Ýø(m@¼IF<à>û†ü‰î³ .ÈÕeê„;T·mÇüUÚI"vÈ-4ÇM‰“Èâ +æ/ïZ¬@ ÇJiŒûÐŒ3O–ž“‚ŲÚÍr—Ð / Ôí”bWÆúçñ.nÁ'¼šêŠÁ<ÿŒÇ%}¹îeÄ` †I3àÓubX†åØ/O¹›Á·ÅPÌöz¢/úºƒî6Úc±YjO×ûJ°G<7ÎMäUt„UŠ;å>E2rñ:¶SŠTšhIx«$N¿Ë½x¾Äª1:Ã`OYÈÁ<0ÊpDËP{Úָ߸‹œY¿vÄ4—¤³ RëM¬ëíÎ »ð^ðúb¦J“Ç95ÏÀ½âÞASì”ú²WÚŽö¹Ú¹n{ ±ÄÓÌ~Æbâ0þޝT‘+Âd³çC’ ‰’L‰ŸRqjŽš£O øTÕ¢‰W¡FvcöQ6C5.Hi%ÊX)‘¯T¬*TÇõj]®O1›(ï$´¥Œf`=ÞÆQÃq±Ñ—½ •Çdª¼(¯HµŠ¨+ꆉ1óÌצÖ&ûÕþ×n°»Žh‰‡0E”íëØr|€¹Ò¸†J#é&dD¤Z®¨zª¢¦©•j½Úªë}Ðt6éf’9fÎØßÙE¡‚ûM™¿Õ¯r;]m§!ÛOF&%:—V±p‚­„q.°¶ßƒ+–GÙËtY(Ëe«’*¹ÌQ"šÚ¨ª{ª~E9«ej9{?Ü WgÔÇêKu][ÝFwÑëu:¢+ôŸõ禑I6?2Ì3Ê8j¦£ío³íF»Ù¾ck¼ž^¡7Íû"Tšs´¶}í'>ü ~ÄßAÛ¡%ͦ$Ö¢”v_N¡D? âjüƒZh)­å‡Ä&™2PÉ-ã¥XpÕµJVK©¼Åp *Dì)ª¯ÊVj¼š¯¨%ªœi·:¬N©Óê*‘7×I:EwÐYz”ÎÓS8†zŽžOÉ–è2}\ŸÐõú*µÖÜÜkfšÙæ%³Á”›*ûý%S©=`+m•½mo{ÊkéÅ{?öó6zçB^¨KhhèÙÐÉе˜i/í‰<ñ»K3¼yü^U¦š˜"¹Ê bpGžB=dÓ+®áíS/ ƒzbkªâ‚÷!sÍÙÇD‚ûÚ²åŠ<¥9˜jl—³ªÚüQõ‡œÕâÌ=ÅQ­±™Ñh©Ú«öH:ÊUO•£ÖhÈ^9] ½?‰å2I¦c³\•îò´t•"œTÍt¶ÌGOWªŒÔ“,©`®)Ä£ÿû駤á,.ùkÍ÷ÌSŒOXInÁ§² ·Äº+ŒnšÑ¨€Qf1íýQo ý¬ˆþÇ2Ù;ŽòàIr¨«×ÛÌF þ…Kv7-*‘ô¢?Ѭ5ç]Ww?=Œ^†Ñ¯Áô§Ç\ •ìc9(¦§×g, >–1£Pˆ§õJ\Ä­qóܯÝT¼OÞ[’*·ä5zD9zâ=¦çñ‘,¢öÿ?ýró Q‰ËÒBÚJGúÃU;Ë.µe¶Üî·Ç¼”ö|¬¦EŸ£5×çÆ¡ +—qCb¨›8¤â§ÄÛØGb²ÊÕû!-1>ÛŽq<½n$ÓÙJ1¥·†þ¼¾QÃ81ûqZ”4çˆÆ±ÿ¶3rþ9Ï~“œ';x¤Q»=¾ä¸J75ƒýõaK+µ*‰é,>§´]W*ãB?Éa[70…ì¡ †Ê6jàm¤1²öÓG)ïû¤Ò¥¼A¾|zhC$ Íž…T°ë¦&ê}œc¿ÆÙ«zÉãDqÇQ‹¦2ýaÄpB´‰È_¢(^RãÝý„?ïcuÒÇÌ +õ£Xµ>©öòJ+ƾl;ÑÊZÝÉu~¡ÇXÕ dT°Ñ Ú»J<™àû£Ü†ÊH“»mOøK§PoÙÞ✣Ï$ÿ\ÆZãwáÒèUe˜DÊÐ ˆgy&÷§3_f¦#‡TJêDDJ&¨£lR_ž{Øþ ­$–›óx‘ü‹½2”°¼Êæ`E´>².•ûËBK"Ë ˜?Â|x]{-¢ûçñB†gƒ<”€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œì½ ˜ÅÙ0ZÕûzºûìÛœéÙ—30Ã, ƒ£ÓÈ&"‹²È@`ØfQ@\PÑÄ5‰â#apIB4.|’¸$šÉ4j‚!†#Ì™ûVõ9ã`Ìw¿ïþϽÏýŸç?CwWWW×öîo½Õ ŒÒQâÑöÅÍóüêç'AÎoáºbÞòæÞxì:„°!óÓEËÖ, ¬}§!/‡Ðœ½m­]|¯x'Bí=PþéùËçµõ~{¡Ž¹ðŽ·­£¹­àí / ÔÙ…ú£åóV·ñ3ù÷Z_ åíe­óç}÷ƒÜO…û[C¼U²÷¤ýü…m‹–ÿüâÊÚ·ü§óWuÙÆõ'„n>0uÒ”òJ³²©¡­£ ÀÜÅË»V›fäu¸Ú×ÏÜÕpó çõ—b"¿ÇþPTJ®?åÚ¶}±«w‘‰$hÉ´¼ûÆ< g–¼pvŸðÈ€;aN¦ùƒÒeZ‚Æ÷¿]ƒö§ËüOÒù +à©éò)¸%;ÓåIþêtÂ7¦Ëˆ¨‰©O—|æÁtyØ)é2 Ùy´ i]LÓ<¤M¶‹¦š¿‰¦Eš¿¦%šÞž®ŸÔ¹›¦ZþG4­Ò2¯Ð´FóߥiMHÓ&}÷3šöAš IûIÎKÓRçÎaæ¦éy—NÓ1Zæ2šÎ¢efÐt6M/ é|Z¾¦KiúšDÓ[IZ¢ýç¢i·­'IZsó÷Ò4 ÷£ô|êÌ3Ü+éùÔñ4±+='/½œ.#¡ +µ3]FBaõñt’8]†EßPÿ”.ârm\º äkøÊè6íítÖç¦Ë@¾þ½t)žòtñžºt’ šŽšQêD-¨­@6àÝ` ã!p®D«§7wt¶´®°kW \¹ú¿.Œv@N%ÜW ¡šŠCiM e[QZƒÚhÎH¸ë€49σ|·¶ÁðdZ6º +òÁû]йk†+iyœ Á;ìÊŠŠ¡öÔÅÍö„Ö­]kÚší‘­m­óº »ƒíË–ÙWµ,ZÜÕi_ÕÜÙܱªyÁàÿïº7bÄÈ£¦&¡xœ¯L€æúïFt´Ì[6àá—)®¤ÆyptÑÖ@ËáÚ–B^+Zøÿ ç¤Ö´F÷½ip×w¤¯6š©yôÎmyä–ÓlZ÷b:2͇»•ð´‹ö–”Lb·tÚó쮎y š—ÏëXj·.ü·p±[VØ]ðlÚŠ–®æö”®y]ÍðòŠå­v+<é°ç·®\ÑÕÑÒÜ9øßi¡±r—‘^Õ¼hå²yh"}y9P+º­Á:[¯~L»›yæ6Lš#Ã[À>ÄîaȾÇAöûì…•_Pë—ir×òµÏ~A¹eP×Àöh‹ÿ¦ÎePfÍÀ{.Á áÆsc¹‹á\wA + ÞWËD8¯¢ t´ïÆ›Z£" ïHOç¼ÿ¢†¯O7RPØôn \WRªtì"ÐB +ø.š× +ç4¿Îåš~0ÛÓA‘£+ àÜûy´¦6 +þåa2(s ­#ƒNóé[™^¸odúÑ1 ,i¯êX otõ#æµ´ßóáüõcpïIÙùÐÚJJ (ŸúêL7–ÑT1”/+!ŸkÒýþúºWü/ŒýËÚКA^%—Læ÷á×` a^د‹ÀˆŒÄKm/CÞ¤~w¬ çZ:òVŠoÿ&Ì»êÍi2ÿ*±“Yí‚r+雤·«ÒPÎÔCJ.ƒÿ nl]i/Ÿ·Æ^ÙÙ üøçÂÖ]vW«½ ¥³m<–h·u´@æ|xÒ ×yv[sÇò–.Â8¯YCÙè²–ùÍ+Hð€ÔÑAsÛ:Z¬œßExíµ‹[æ/Ð\[VÌ_¶rHC;Ó‰ÖËÖØÅ-%vóòk î¥Wü—­Óâ ZV,²;š;eÏ'þË\^Ÿ®ë":¢âh¥«y9-Ðê‚ÖkW,k·àÂI˜ç„A¿Th]ÙÕ¶²Ë^м +†LÊ,n^Övá þ?äÿÈÿòÿŸj*ÿNúk<_-7üëBÅbø¿ÕõÉÜO ã&XÛIµ¯.T’éápW+L«=ÃZvñ„–ù­­ »JþßíÌ×LÉÔþÔ(:)×ö£æ$xga? ÑÖ‰†æ¢áÄ4¶Ò¾t¢2È›L'ÕE势qMÑÅyIœL]ù<þ{>}·÷ÜñÆÛî;üÃTvʾ ýfG+fŠMFVLŒ¼2éò‹áÚa¿áéé;ÝmšÌ4H|Þm4q²[×iâÏŽ¡(Ì4Óía<ÏzÓ}LÂï+ýôå!«º¨þª‚¡`Àdz7ÂŒç^\´vãó3'¼žºŸÀ¿þà}·Íüå¹Þw?M}–’ —;Ÿy zF›ËU¬*1S8EÖ<†i‰‚Š™p0à÷‰ˆc¥WECí>¯eº¦ú9‘•°"ð*B¦íÇþÀ®ï =ø^G翇ËW"‘¶­.’L8C¨ ¾·©¾®¼ÞªƒBz%—!¨ÉW ÁqhmHƒ!±°H‹†Ö:ƒ¹Ì‡ïfý‹n¼~íÅ­«‡Oº|ت®ÊÜÎ;‡•ì5ÿÞê²;K=5·L›tË—O»kp„àÒ3©ßá›Ð1¤ ‰û çïC×&;…”а‚ë‘°pƒ„aâðIh°“õh;ñþª„ø€ôΜ4O²¤>eöR¤RQ(ç'=Z{àØä«+놲ǎµß^8!2o´;÷0K˜åÀAÊœHÓÆ2ðh21Q¾ +D¸¶;Ȥœl2?DåNÁØÛˆ59L îÙ¿Ÿôþœ¶@ïYTà„ÒÙz·‹»·žoçh/Ï6Qjp;uè¡v`a}dê²,šr±}¿Ûë¯czú~çØþºûY̰°»X†]EÙ@WPNa?BÌG€—; qnßZ¨¹Þ¸cݺ;nXwyóæðÅÏÎÙ—ê{7•JýtçÏáï¦îÿËi¼/ù´e3Á•÷€çv +ÚåØ,áìK¹õÌ]̃÷,‡e$ð +óXcð+ +í½BÆ„0ás=}'(÷‚Ä'ŽE§õP€Â,;® L(|¢ï€,á33QÁc›wx†¨‡p=¾¹¤Ñž„yIº?¸!2ÄaDž4¡¦dNž%b Pas®{ÄSïÿÏò.îºKÖeÿ`ì+sÈØê—E[¿”Æ%Ù2õ°Ï'LÓ *YM|êȦ ©„ŸO ‘‰yšˆ{àIB#=Oô0‡QB!;Û´@Xg7(ó9Cå§HOÈùh%A^¦¿AÍëehƒŽlXL¦Žêõ1Ó~’GêÞ URQUfZˆp:‹_×ÁgÒi6æ ½ˆ¿H8Ì¿ _’^Ž‹ã´Fmªg©¶À³Ö»Öw«÷yïÑb§£Ú ês>&fÆÍ,3a +?ê;D@~ ®2@+šPLI^‰GýñxTŠG[HÑ8«'Ìæ‰}“,lõàð~2D§ÃÀŒ¦t†Þ€Ù&¸Ž3AÑ2ñ0G³ö70s˜Vf=Ã1‡˜|”ïÚã";ð•³IÂ^ˆ<¨o8ÕÛtÒʈƒ-žÁI°—Ó¢  CM¸©£±± SX :´¦PŸ2a  `Ç  ‘Ï×2¡‚ÇúËÓ^wãwðAßç¿xãìeOýä±Ù‰;GÔÏ?rÃÑ.ýæwnó½þÎ';g<óü·Ì˜2½ïC.˜’ÄiÀ©‘°Cæ?G˜ jRƒ\’§è†f$¥$ˆs‰’8_¢çéZ8âÝ6 òÛb!")^XN¸Ï±rò‡¼u DNüN½h¾è­3&+ÉAàWÌëA}´¾YçF[W[«bìUÁeæÿ‚àJ}³~›ÿÖØ÷t…·YŠ7ª¦{8C»˜€ÅÆa0t\Ó­i.|ˆyE˜ÅNô’‡nêÞÎ9v«ÍØa‚Éö±³ò¦BŒ +ÍBz|æ9ò¤pÛ p¶7ò>„‡ 9â¨_r«²|Ïž âP$<ëL²Éå[½' r‚œ$ðtÁ ¤ +jÅí Þ Ï¢€kû“ ŠäŒòr §wgß»tý®Ç®¯ºÂïU;{6/iÙêïÎùä«_YºpÁÛR½ýã>|SøÁ-»o\÷¨ÿafõõóoÜ´ÉÞÿÒ¢½ æ|gpâ‡wIýýCètx€ ¥“Sè õÎÐki;´—5þ +ö +ý[ëGšÀм¢²"Ò€Ø_a9?Ër¬ŽM=ç0sI`lwÄqP½¢p=ÌÂçx^q²²«• 'T\ÁDŸR ¥ôàZGܼjqCN¸Í`:©º¿1&c3,C^&ï@âäò³ßÓƒ·Ò™þ3p?ÊÏöRo~hR>hž©?[oÕÕQ-xËà$$cL÷ÈÙ3"d¾·XΛŽZUÇæªc¹¬¬zRE#Ê8~ÍQë´ “ë4§°NËÃuPå¶`ðÔà*«*g±fîëÝÄ|÷›/¾ØªÁs¾Ç8ù÷RQßÛ»ÈþþIà±Ó]Ê9ˆ0ŒO'Âq’â^Â*Tƒãq݃‘yA5š TFx¡‚G€D½G2a”x)ï5èy|tMÖmY÷ùžòýT{[ûML’}aOi”•+ø +õð1¨Ãô)¯Ï÷ŠÇð{|~¡‰8>ÒdziápºSÏ~ƒp5Ç&ݳ昭æzó.“3H”HÂ…Í0ÎIx›í}× ß H5l¯gÿ×Kö…Äò%¹4h„´É‚ƒXx[¤ÁI ˆ(ã£<·ƒ¶uÙ­ør9,Ð +øEÐ +§ý0ð಻wn½zkñŽ;™wzŸ›´éî#XêºãÌÏ{ñó¶Û>öÐÞI Aæ¯Ï¦VÍNýÅKwï=A´¶ ¹ð¼,TŠ'¥¹^¶³ñÌâXqÂѱ®ƒ¨Šñ¹ ¿®$0*0‰£œ™™‚!ÊóBTƒ ¥Õ­co3–dÓ)óhä ¥Yº|Ë©³ï¼“:{÷5›—.¾ùÖ…‹n>nÛ”Oï¼qýSl¬ä%Ûß}ûÂûKÊŽÞò|ÂøÈ]?ÆSoºiÎü-›Î÷MØ6éÉ 7>ótÆ–%8™®øƒ4¼Õl€³ DPî&&N1hØ¢ µ¨¥c…­²¤Zœ ž›IÖãñ£ÉS5R7ÁªÀDÒä%šÌÊÑdS%e"•tbÚýLÂEßûY¿%1 _ÊN§” +O‹bñ¿iõ¶¾ÒTùÀ†œêáÑ+‚NÞ¬àÕy ÙeÁåÑEyk£×'¶FoO<Ü}>úIðCû¬í»8øppg^²@`ŠˆÜÍd +çØ‚]œ˜ä™C„lœ4‰ß˜ì²änÒ‰ìC¸©À‘­ Åê¶2§» ›¶úqÉr,ÆÚ–|i ¶IPéÔ@Ù™a»¨©75¦%å%LMuá¶pE€L^‹šÌ…˜¢L€âRÛÎàºyS®Ÿ<=¼üÀy,¾xשëÖþõ±gße^ý^×ê½;Ö]ÿ(žb®]qÅú_·iáéK±ôë÷±ùPê©ÏRLíûÁ lõ·ýÎV`¹€3ÁüÙÌRŸñ0Ð#x$ˆ2#Ôsl=8…©½1Äb~TJû–Ú ÿk€ÂÁõdG&;Æ6;vþ©cÇ nêÅ¢u{ÐÍNy§z“úMõqõ´ÊÃ\*µÊeºÒ¬ìWþSUÅ#’6ÅzAà=œú}…x¼òøzŽvc#B¼ ÖsÊ0u8_Î5pŒÍaîQ#Ó¥ú3'A»$®.¢aööž2]¿í$2_&Lu´g:Úï;–v‚ezq…Aï[SWŠoño¡±èjôçj.Ç´ƒ995z•g´g\xTΘü1ãÆNŸêY[â ”àB¹4«°¤&:´ndÁôpcÖ¬œé%ÓÇ5No7,,Y]›Õ‘sxStkÖí9[ +#s²±Sˆ`SŒ¢ +u²Ê¨bð0s‰Æ3‡»Gg•l¢ç Çv²-É$á ¨ˆ9| ü²|CÄbs“c˜“/AùÞíF~…ÙÊÁ!¼؇»†•æCyå1;²]ƒk"3®v™MNõ•®éÔ™^˜2ànå§N5}„Éjh: È™–ŠÄl- ÈHXuá„j«X k‡zkª™ü¼\Ž ø½\•_[%\^n~~”®õ¢œJޏs©žWTˆýiì<ö0Ü­#½²ñé–Ç?ë¸úáºÜ}Û%Y5Ó;nþ~jç±OR׿õþæß±€¯™±¿êóÔ3ý]êÖÔç#§.X‹ŒÏñíó^;ðëÑÓüz*xãÔaëÚ/Û2Ïi_â<>~Öâ_o|7lŸÕôíÞy[XÑÅ“±~×S8÷¿I-úä漢w쾡åÝõÜûÃßœyØ~õ實~÷ûWJ‹"øŠ[¹éÕ…·Ü7bÛüûzåÁú‘/:€=†I͜ϺӉÏ)eˆ\n¤"•ŠGžžËÍ +s‘´XžkÞÂn3_æ_Ž˜§MUâñtf²¹XÝmþMû›þ7ÌiœÎyXU‘yŽÛXDQƒ´$h"Fˆ¬rÔ/e‹š1,Kò$µ9ÍoÉ ž—+ô0mŽŒ$íc‡Á s« .Tǫ٨Yd¯šÌ½Î½Ï±Û€pz0vÔÉÚñ}ݦaÜ›†øºÈ¬7ˆŒøMãí_¹„þ…;¢xX¸¡> +ØROü˧ˆw5 šÿ–Áazu×MÀ¬3õ=º…w¯@}ãw«SÆïN\9sF7g°’x¨ï4YÊ!,°w´7¹Þ²<\…óØÖ—Ã:ËTý‚™ñÞ÷{¿ýè;ø¯ŽÉWñ‡¾ƒŸObfâû^{ÇíD»ôÆRµ|0K¼¨7&ozÞ¼Ny“,´DWòm2p!þ&U( +Êl¸¨4Ì’eŸ7QZZR‚âY ˜·ìDÂBR¸PЈ&€UìT¡%x‰À2ó‚Dj(¬?ÁajA¡'oh +)§¼RZ´,+aS§£ö8ž¥R&ÒÞÆ/º)Ý„àúêslJ^4;ÜïO$ UéÍ„SgÒ.Æ´/ +,õÀëÊ-²x…]'ñ7VY9¼&çTºŽ¨Â<0™+k)mBú>¦ðéW;.ºù®«7üxkê›øâÃ.?æÆ‡S¿ÁË¿Q8ræð©÷nMíä5lþÆ“UEÏoX´gîö*+¸p¸֒sÛEmØÒ1W­B|˜ ûþȯâߨ¼±>³$‹Á®ªKÇ÷‘3‡¤lT©ÏGm¨+kÚ”µ =ÄŸýž~íÖ_Ò£“Y˲<Þ,++‹-Š­Ò¸=VŸî¿:0=²˜_šu÷vïC샞‡âOã'˜§­·<>äGQÓoF9²l°·¸Žª.ƒŠëLƒD%ûKp²Yh\Ž +Éše4;ThKXÒHo¤HbþlwÑ%L4œÓö’r›ˆL¥¢ &Λ_ÜN,$BšðC"¦¹îŸ\œúé§R¿úö.<ò'¿Åe½Põ“oîøÃìån~ü?fÈ_Îý¯øåxÚž¯Ú~Ïc©¿Ü}8õñmÏÙó0𞙀ÑÌÝN¹GJ.vZfÂ@tYÆÙÔÉ'S¤’ºJ¦9õ(KŠfg™ÿmÔûGõ>Ï ^⫨—N7}‰rC*F®q†²1Q$^â$Nˆ„£aFP ÄEÐôY!Ɔr°×§°ÏÁAÅÊA0‹Éd)ü6â&‚¡¡`(æ&øYS™ö”‚-šó0þç÷gÞÐØÕ9qíÝÇnNíÁuwoÈè ÷/›¸3õ(uÅ5©×>•Jí˜W¹sèÑ?ùá?J0êÇ€3|ó¨¢{€À'$IË‘‰T䄊$‘`G–é­§²—ÛŠ­3JTçäÿrÕ.šå"PzÒ&P‚mšpædò«t:¤FÈIqùçf“çßb7ñ‡v¦žMé; jÏÝ cÑN’Žá.ÿ™aÀ¾c3¶Ê0Qõ¿ÑoGuW­ÒD˜ú—î+ÍÐýý?éÍDwýjߟfß;ÿ³»w2é÷ð½ ¡/@×7B¯YôÚ~ÒK†,šìv1]<ÙWUí^U¸×â÷šWà^³î5u[Ju³Úæ·ñ»x–µA,Þ…¶£Ýˆ+GšŒÞG§ïµ!sby×CI†NOß3ÓðifÎ:¦+Sé4<ƽÝ8ÍGΞ±wΦÆöŽúÞ´`J&‰ë’ ºÊzá'DÁ‰Ü)¥+þË 3›à‘DôÒæ)Çö¿Fgÿ…ô„!½›\üq;‘¸ï'Ì/¡#Û M<€`@OLædÆ“)Á()°%nQ…† ž,“”æ%yCce„IV=H’EHU“ôQ…> ¥T÷pz$ŸgFr¾û‚åOâth8rÄ<~üñ '“”c&Qf94[¤3.Ð3KÏ=óô,våä‘C”Ȭ0ž/µ+…žÅŒò%‘ ˦Îk¶â­6è‰×X„=@ÞÐ98©&h%‡™éÈ s5ÝÑÓ” d¦ŸV‹ˆ #y¦ˆ€®!×»ƒirGCˆžcÎzÄ’Ÿ‰IÜ*m³ös˜Jmœ6Î`K¸½Ì3ƒÅ­ÒW{¶è’ÊðR>Ô3‰ÏŽi‚~©Gy€y½O¼Ozš}J¼ŒáñTðŒŸç ,ò +^‚¤¤]e\…Pç$IVTÀlÇ$pšëÝàe¼‡˜§‘އìåm©qMVlG[¯bõ ÒƒUxÂô€(€ˆF›‰Ífús6?—ßÀ±0Oï³.옦ú0àÕó í¿9ÙZ_C= ÉüEA$Úß–ë©ò°¾Tò~ˆ´¾s€ƒoƒ"ý6ÕñÆïÖàY1<#>þÏ÷x’›vù¿y §ÎS–CÝþjë<•µ4¹ä¦]ûÉFÐQ{±–ýq04´çXyÎÃÖ8ϪFjðÌNMß•šÁ:÷ÙÝ—Mþ6{þ‹1Ü«çj¸ç1~$k6á¦øú=^•. +ûÕRX RÛGNII fÛ¢ +·Äˆ,+ÉÃȢı6X§ŽÂt¥¬†w) D¨¥èÜd«Ø“n®Ú¦nPyUÎLW uhì¿Ç¢¹4‹þW©¬\4€]%›’„9€’î.²Ð8¦†zjÅÎQ¹äGâ)N<§YÕ’ 'ÀàÆ!D º%gL ÿÈ1u’Sé&+ëÄܾ8d¥›$¹ynL†šW'züpøÈý™>Hf¹É,HHòó=4ü’xé«0°1€Ýw^b™C/OÀ6rëXÎmHë@\/@JGa´×)k¶–ú™ñæxÿ,s–ŸSµP +…]è-”¨Ö-™i>rƉ‘i“¢vÿhXÿŸŠÆ•ì‘,9­‰·7¹ºx¿p¤*ѹ©B“uÉɱ ݯË0%÷LXvOã§©—S·à랸éŠ!›R·ò‡<ÞæË§z{ŸeñÖõ³o +èDúÏîû#÷'С+˜€S4ŸÏu²]WPTÃÖÅG²ãÄ+²FgÊS4…mgg]]|«Ï“GŒT2ÎüL¢ “(Ì$Š2‰<:na7QIfEDbŽ!©b½0ŸÉg‹ +†Õy£ +F—Ï´§çM+X¦.Ñ—zú›ÃkÔµúZãzse~gÁfö6õVý6ãóæü› +îÑï3î $Ò’`PN¡7V• Kp!B%Q/W9¤5ØõAkb·Æ˜XAP”(*À|'ðt­ëÄ 9‘²T#O‚ÓGúÒDWmËO¹1gPA¾GWùЛc’(p,#à‚ü\È%*6(êp߸q*ˆQ+R±‰m<ÏÅmxpÞíø‘&IÓÐãËåBT‚Kˆ'Óãa¦•®éä½’h%Œ z { ¼äòöñÞ©#CæÏJûbNóÍ@´>¯hÄP864P¨…(g “Ÿ`9Э&_f/ëÃÐü>¯7 …Þ¾ú}< +Ûäªy-ruf@t#ž„7#?Æ(ÌKR"öa¯&ˉ€’^K3 Û´ü¦iyeM +xÃ25Ä@—x6l†,K} +{½–…¤h(5GÈøJd# Î8Äã+ØÄ%‰ôàÛ÷<íòˆhdB/¨b½ÑHoxâèæQ¦ã”úU1”Wθß& TÌ.¼±lñ˜G©þh&5ðÀ6ØÁ ¯BŽ\ (€ÌÒ/1 ­ìy gŸæðÎ0):š!|.Bø¼pñU‚Fœy?œºî¥÷ó£Ãúä—“òâƒ>üijÅáÔ«EbÈŸz™?t¾áþ{ÿ”Ïþ®7šúóßnïfÊ@ÓV»yì¹ÇA0\Þ÷ç.AŨ–ä”ɺ\Ñ£¥%zi)(ÖÚØðÒq¥MzSé½¥tnÅmúæ’‡‚ߎîÐÅ·Sâ$©'#ψ.>y½ø—÷Š¥QAœ ¼Ü"ŒÆëýÒ•[CV§‘Tv(;œ,+­®ãêÊÆq—•M—“ ¥–ä*m‹ö²öOýŸI«¶Úƒ9³<¿:T™ãÏ)i-aJâåžÏ]žG<}þÏ.Ï_<¬GKÇK’‰ >ãHŸ‡®Äy²RçñÄÙPóÌð½þx\D¤P”²ÁÑEJeœUKæ™ó@9dAN>;i¡ÿgW+ÍçÍ'Ë»d½8Ÿ¬ü‘±Câ·„ãBŠ6”Ÿá¯ù=Ì,ÇSä¸*»°¢pW!_¼ˆJ3PÞ>@Cê¨"›È«®¨;RÇl¯Ãu!Ò·¤ÆPA8·<ÿáuÉFðP×)]¤ÂÔuJ¯ªf ê:¥ Âa‚&A&A©MÒÈ…Œ34Ýäy2™ ÙÊ”owLè¢v;BAíî*Œµô¯¦ºÈ H¹„¡’2øƒ¡¼BV=Œ»Ê…Øú—ìz~lçe5Kß]„«Fß²~MÖîðŠã·ÞòÌdSå>]s´uvåò–ÅfÝ4mÌ÷ož¸q¢ß£Gó ”ƒ.nl·ß>Þ™wùàÕ§ÏÝ|ñ0ü^qÜ,žP~ÙÜY“.¾0z3`4±EHìñçۘ׌|¾†Íó Ù»³™ììÜxUüÒx[ö¶la¸¯>X½"xE´IjÒgMÁoD—HËôÅÆŠàŠè‘ìw´wCïFþÓ÷çП#È:‘Ý—±ùr£Ü_Á7…1™_È¿›õwî S3N`P,D©â5œ\Ŧꀽ²Aå\¿¢JqT §Ýg©LVI\œš gVIP ÉqÊ <Õ.ÐìçZ1T\W± sƒFµïƧ1—ð$Ìb"Ð ÒbfEÐ STÁÔ?ƒ½U0ELŒs‚a´h4ä]LÝï8’[;ÐqE±¢£~‚Ù 9 L}™Iu,øG}äî2W{jÏÉs4'PÙM”—[Ä‚âôåjý §º;ö\³«ÝI}öÃç—2ÕÓî^õì÷V®z–?Ôû÷»&ÝõJgê/©·¿‹ï{aÚíÇ^=þâ1v“û>bO¿Šâ™i £Ú³ÞÀ†Š‰ÓŠ|õ‚óÆU1çTì ˆ½HG/Ò5qÑ$£)†{óEW3<ÚTI¢ˆŒ•5œéšâ›šë›ú6ómö!ý ó‰¨&ée ÓÂ.áWjmúýIm¿|@Ù¯iAm³ö†õäÎ1Zõk``1Κ +êI› ÝÚ†¶£è4(H†¡¢/û‡®ç{$ÊŸrc0¾|5™ “¥$ ‡Bç2 +“(…ɸx ÿug‹ "#zH!Q!…DÊ^Å!±ê£i  âSÇø)yã©”ÁDÊœê8“<Õ‘‰.±êÊͦ“ðêÁ·Frƒ“Ò ”—@Ž­ß“õ—¼›úGÇÇ·îümö®Èú™·<óĦ%wâ›CÏ½Ž³°ò,f6îz4¶tÙOßxû'7›s Àì}w% OsžPN/ЫõQ:_㯉_ÍLU®òO‰/bðÍò|ÿÜø‘ì7ù·|ïE>ð}àÿKèO‘(å³³“QB®ã£„vÅÁL¾>88œ©ÑÇ3£õ1þqñ«•éú"ýáÁ/ð‰¬G5  HU´$«†«HÄ„Q`šÇ-lZŽ5×Ú`iœp Ôòʱ¨Ð"¤j ƒ,J°uM·Ï»<:/¾ZX8Ëœ ›(ˆ =šœl ²Á¸±ÍÜn2¦ÉÅâ +ùžÏ3c3ÜìPÌ» Ôq¯¨'äè u©9£gÂku"ÿɔꤼ\TZ½[Çz4›8¾ +«ÉÕAÄl6ÎV™ù¢“_Z”=Rq +)—ÀâF4@j OlJNè=9Ñûñ,µ!'¸)ÀÝ”úÞöútLGzÙHÐŽ ‰™¨ªY~1‡F1á%(°ß8TöéÁSÁþß¾…=øüGÊÞ›çoí}—¹R6ýÖu;ðôÐãÝ8˜½†‹S¿KýÓ´wZŒï݇ pˆeîÁÔÙ¶oȰjêtKƳ«·!q™DÈù©ÅYL-Î\B8¨,mk~–vùÓŽ O¨Ø¡îoº!¤§ï ‰GžÇ‡P:‹N&Ï&ñÅål¯SMÄ­§ûêÈ‘k¿i ²(H !™²7†,Áˆá$N–n܈“@'UV^MUMu-1æ­® QÍ{yĽiÕ³cÃ*¯õúëìC[Û—V¹Úû]eÌÜk¶ž_qiêJö ÖêÌUUÞ_¦ø¯PGû9+’U¦úËòêÔ¡þËÕ1þéâ u±ú…ò÷€gp^YÑ%y—]Q´­l{™84ghICÙuLÎè’©9SKZÄù9óKæ–m({·è£œOóþRd…‚B ‡ÙÓ]÷‰T’˜6ª rd:‚Ž#P[™ëJ>7”ѹqM ª +ª”‚pøx›!'47´!Ä•Á”3ÓÊ([ Q¶êgk!ÊÖHH+ÍýÄek¤ qM³µQ +.§Q¯].@¹Ùù/¯ï}—m4“@ÐQŠ1¢¶F. ³Œ“šÜðlƒò6#’,ëÊ!ì-9q{;sÊü +‡ë=y–DBŸLtt÷2¶ƒP +‘ELª@¹q\„Ï…jª,ê)¸p—Z9²ëú[¼j÷oN¯øÅϯ}²ù7ÛôɃO^¿îékW?=#zeAå‚™µ»oÇõï=€ñÖ6œ_òùë«¿Ï–þâÈ ¯ýôÅŸ»z B,YåôãyQ?ª¦»-¨z]ÀÕ°£ÙC:G³†‡"Õ!ÉÒ,?ËcdÄyѯ*ZìT ­î“ñ©Œ :tY¹˜žý21,,ºÀLu;9JÊÉÄ7HA"û Hd"`hÐ3Y’¦÷gМ‰A[=´zwðti nîö¹ ã/pã&ôá4Ùhæœ@õ¦¶_8!J¥®Z)‘¦û]ä_¸ú b(Y2Tåœ;y€·–îâ~òä ‘fÓ}‚T$f4¥Nà <‚út‰ˆëz#¢ÆÉ*WK VžEÁ(¬-Ý7YõƒñÝ+—N¾£TÂÏîizâ;½s˜G·\7åÎë{MÞ€ª§ë¡":æ|CJF0IÞ&o—wËGä÷åÓ²ˆäl¹MÞ ?’Î:!÷ÉJ¶ :–È1¬,°7`$ð§b¸G¸íÜnîw‚Žp§9q6wî8ÎÕ•™i\ÿ¼qtÞ8…´ÊQÎÆe8—ñªq„ˆ2‡ÜD髳×AwH“™¸7º©£=Icê`Vnéîîæþôúëç\á¹wIÕc©+ñp:f/zËÍñüE\¿™çCÏ‹Çp¼a]eX¿ÆY¼*’ª‚·ŒmÀÑC! J½@Q¶©8[mP'©,YtjÉˆÒ ”ÔPP©M©&¨e¢‘A©µI(m«ŸgÎØTM©lƒú‰&q©´£† Ä&€QyëúÇgUUm1%7ÂÁ#™F¡d*1,{Är1‚l|­ +`w?ÌÝÕ°¹;µ8whvíÐîª÷ã>þÅ/þy݃žq÷p³Ïm?:a¡WÀös•ÀÌsb‚«[ Ó…™2kèãÏ +¬œ =:C­.%“3 ºRE÷$Nc¯U¯`ûrª%0Îöy‹ªeb¤ÁÕËÓŒšál‚ãxN¨•Ç(„AÊ åZv¥ò.ûA|RÀyB¡X Õ Ãä}’ÞÈ5 +3ÄFùzn ÿ ü¢ðKîmá¤ð±øáŸRÀ«(<ËrŒ ˆ²,Á,I¢àEå¸^ñó¼¢ÂrÄùÉñÄ妪Ház°áÈ^ÆÙò&–‘Ã:Y<ÛÃݱä(rYV,eeÕ $â(«.oîµéeONz_]¹lGÉ$]ðúŽìÍ¡‹l{ƒäò»½fà^èF/{ÔÌÊ'Y9#Myßã°äBk~==Á[g÷†ÉËÞ«K/ +4ºÞ²<@ùÙôœ‡E PüÌÇ©%ø…ߥ]Ï:ÿ<ÞZÕ»€É^›šEðò&8ÕRzýÞ2(„Q;Ì Æ¨®q¯CÜ«û†#NˆƒÏæáßç¹Ip:ͳÙ|¿ïã9àæ +ú žÔD}4›G>f&3Ûþ%·ÏÀí]X»ú˜”VÆ2 }}™¥4ïB¹ ya^Äuäp`zG~dfnꦡ®  +AgÊÃ/‘é3™ø‚3™½Ú¿v&¨zuw’;)ÿ>ôͿşµ™dçÉá˜-³l^".ˆJ!b!/1•ãx[Áö¦ø˜§`›…-ŽZlaj­Q7µØü4žîÞ%µj·Q6fQ•Y´2 +Vnr´pÁ¶ŽÑêbýÕÅhu1åa‘êbTJƨá#´D…sL#Ç2ž¿©/ˆ˜ª¼|aâ`²¡?–Ò_Ö¿Ðå¸(˜–Àç3:òÇOE± +K’ù=xõ¾¯r`×?Ó{r€Ëf€«nz©»»¬PžAôP"¶èfÛºŒ Öü¾B¿fŰWduÚt!»éRXˆîË âšêÑ÷£•O.Yuö ¯<ü̾¼Ù—´}«{Æ‚+6ç +ï8皇vè-b¾»lÎð{Ÿè½ŸÙ»zõä‡îî}'£s}øÄ×;>ž|ÌÓfùö¾ÓìYŸÀ–[³ÆÄ˜ÇÃ'Â}aΖüÐ :‚º¢{4O~˜êYaªs©TÛR©¶¥ök[*%5—– 3Lµ-•j[pÿO ª’öÆu(;T©B§bø§N ¢‹Í+|:Ì´…·‡w‡„¹0ËT‚”6Ïv[V:$ék.å+ +—5@áâÒ”xÄñ~U›¢[ú@…g¨vA.üÜUÔܯ…KV$ETXÁ,´O Š7 d2ØN¸0…rÚ‹;Ä[[ùÞÜG'›JwéÒË:Ÿâ +ïß5ºmBåõ½ÌæËGÜóZ/³Õ÷WPÔQ/= ;Œ}dµ€Ú„$;I*BxE%¢.“¦ Ò"¡E’ªÍáÞáÁšðhs¼w|ptx6?[¾Êlò6¯ +/ç—Ë ÌåÞåÁákq@x};•ŸªÌÒ–±Í|³²LSBqN´€eøócÔö‰Q4û?I RgNÚ˜q½ÒDz?¡»I‹&ÒQG_~Au…ˆ‘hŠ¶ÈŠCÞAòÇW¤=ùHó³—Æ3#êkDq +_êBHS-å?ˆî'DTI؃†D‰K!ý)rf{²élSÓXf¾Aü=DlÉSø)ò5ü52Gd)⣛ Qzká@£hÔ·þì78xÝŸn?uêàÞ-›÷î»yË^Ƈ‹î\•ú}ï±?݈XíÕ×~ñ³W_mIµp9A/Jàkœ;5sy±9ÞäìÝ6“m—hyY•ʬK³Úìm¶4<4Ö> ›%L®– Ô15Ú8fŒ6“iaÞÑ~þCðãȇ±óŒ9Ý«¢GðÇ9\ÈS…ˆÕÀ¦ás — Žˆ…žAF¿# Žƒ:" *H©+!Hæšîá¤R¼r.ë_ý«ù”’©B¤>1è*¾®¿.+q¡÷ák|«½gêÿ0¨[i?øÐ´»á¯jYéýÓ~˜úKë7ü¬ý±ÞœgWw>¹kÕÊÇS-ŒtÑD<‹ÛS7=yç#ÙÇŽýô¥7ß~‰H¸›4/T,ô²sQ¹›Î㪹‘Ün!×Å ²%É’¬û,YG¬„UJH‘‹·IXʵ}ØÇäZÿÞ²ï×õ>w¬‚F ŒèÂ5î…JþDïØ£ÿbÜŸ4›Ît(e25u™ óå-œØÔA¢Ì]ôu=j"Š›»¤¥aÖ7.¹ôÒ‹¾áOp…¶_6ü©¢± s;zß$³ÐÐ÷»f¡‚ 9×q¹þÜáòåò¨üé¹Í¹ëä;åMùOú¾_öV—CÑp¨b|ÙÛ!>ÆLc³+áÙÒly¶2[­ÍÖ—HKä%Êu‰¶Dï.ì.2H¸N~ÉÐü™J£º pAqW^Wþ†üo*ßÑî)¾¿ìÞŠ'”ÚãEOï+üYa°8£‰æfy™D~&QìZ‡é2$‘—IägY$nÏ›¨›)h +µ œ:8+J\w¹‘2ººiˆLŠÌ‰ìмŒHv¤5ò~„ËŽÜa"?Ø/¨¯Ûñ“â& N5ñq0ô°‰É#ûüÁj×1<;kY“ˆœ»Mfœ:>`.>XÍŽâh~Äñ…«+ÉëåÔ_vÏ„Z"ô›K›¼±É[j8F¨¿;ÒÃÌÚ+æ—«ûãuÇKq)i…¼QšùŒSi†N!ñ Ýë\¥Må•VÏ­…]}—¢œíÎ2°v’ °éŽlÒ ;ß  Ø Ý3ì4‡øÂ±)ß ñËi7cîû³62$íœ"O³bò.ÓKßÉdû€}>Iw%,I>žÓN—¾‰-CÅÈÅ]ÿN/ƒöä Jäñþ²BËôš>“ru;†äb1†ùApJøá6Ç“C¹yº&•(1\\$+B’‹¡l3‹èYäCZõî‰Æn–&7n܈°#âÿiêÿÈCQaÑ`¦¦zhí¿ Á‰„¤І½Æ­×­[]SðÍœ4bXéÝS®ÿáLk·ÖÙ²nI0XÛôÂýÓ[^¼þõwðÅñ¥Í£.Î TŽÛ8qìšâìäe×- +_5ûªÚ¼x–Oɯ±nöÌG®~–Ði~ßgL)ÿ +¡_D +ÙJPXM?þ7"aMW0‹‚¦œ4ݬj˜¹(ëÞ ÷‰Òhyô\±MÜ n9šÓvq·xD<. +"Ö„W‰®°¦‰ÏhPD:= \ÝÕ ]ŒÈ~âÚI«f®V)b– 0ºgáWŒTúɶÞzó$áð§HüáðVUݽ +úUAÈ]:#+V-ýÆ ÝQΘÑ+ê¯YV¶iÓ¾ýû}ÉâÄ£˜—4?ÆÌߊÅe©;¶ö~sBY”Ú÷ÀËNp…Ðú¤ƒ(JÖœÀrgl_°š|ŒÀ©òú«“>œ/ù‚öU`æLª +„CÄœˆR[%D­”—ºåûƒMB”}‡úí“?í O{ƒCÔà ûD'óÑÂGB841JýÄ4‰žŽ2mÑíÑÝѾ(Õ +ä~ÁA¾BfËÇå2'g‡Ü/8ÒÞh…ú IýT^ÈÔ6‘©3Xž¹À%@œ¾ÿj„Ô÷Ò5ˆúÌX ¢(gztC'1cd{"œCºd¹.ÀÒÒ áÝôªfQ!u†(AP— Û°î­o<>ÉT»UkÅ•WÞyQ÷wº/[>©¦“¹§wßCÆ^9å®[˜ºsït¢Ä‹ÐQð'éx/!E°Ð˜OãÎË“ãiXâs5˜ZÁönZÔ:us ñ¥c$ Ä?Øáå²2;|¼D3¥ÜÇ Ò6Æ›ô^«%)ËÖÜGº=V’²z¯X,FhCX0`6tnqÕ,Ìp +g)iï”Ëè,òAcæÛÇÌ7éfŸt„%ùbˆúq)W¢0—[³¬;-Ö²ÝÏI¥?ŒÃea;rvNµÏrýÖÎsÙùÕœ É>!&G¼<‡8A•Uä5‘õ‹q)¦f[ –JIO5ª‡KyF±cGœ WGc­Ë½³Œ«¼KÅÒ"ïa­Ø%¼ÎÉŪUŒŠõ"O±Qä-÷CµÞk¥ÍÒìýÚSøiæiõIm?: òüœ{[xGþˆûÈø£÷Œð…WÒcžMÁ Ó£"ž½i´)ƒó"K¥Ñ(ð3Î#²:Ö +ôž¾·ZÂ¥tÀ¾Rj«éØïÕ*T’ÖTî*e¶µÌZgÝf)–Â.p¸€ùj0kyòL¹nž$®ô‡1ÇÏÒ W‘—EE1- øûø}<ò‚Î2ÎY¨û§–(Ù¢åõ&yÑÏó¢à\ {üºî‘ÀÜI*’^'‘¯iJA ½œdXšG§Ýó'û) éx ²CñŸ5uâêoÌïIwS ÝE +å'¾ ½e‹X<>uøÐŽ®jÇÁGj.>°+Õ}xGɯ€Á|û¤õ +³¢÷W1 ϽˬÛþuà4È¡¿§1ñoÓr(``UàY`0Ò ¹Qž¤HI÷EÇž3¼ØÈÐU gr¤n¦qwŸô ç!ãD8"¾jȆ¬‹²>9 GÍ<\݈ïT¥rïÕ\£Ø¨ÎðÜPPŸcz´Ÿ«¯x^3ßeß’¡ÿÆü@ñfˆKÕ×2Â:(án’2ÄèHQn|"(lÈ ø^(¬(É2™çXPù ç:6 ÝTA©`t•ÕLE0C1_D/ÊŒY€d?B2Ëè/êX/ÐX¿¦±Š,³,#€% iH™äÅÞqú Z®bÌä$ÃsŽ0YØ@?Á0ÒñØì Lî$˜ËqÖº£éïFRa²ÂüÀÿÅ÷6_teÉØàœ)îÿ÷G}Ed/ù×üFÒÿCŽCt»¨¿‚â’ÒdÙ ÁåC*«ªk†Ö«~QýÅ—48ÿ÷m÷ÿFüʒߥÿ“ÂÑT¶x_a8ûøól :ÖìMfe‘›µ÷¢l§‡ÍÛç T#±dŸ[9=Ûpn…c/ÀÁ¡9,ùR… çõpl€c/Àq˜k8“§6­p<Ç ò„Íbã{ílsDwÉÿ¾h°!ô8úàø¿»ð¨Šl}ªêö’4B–Núv:i%ðÂfÒIº‘¦™ p *B3Š@³Ïapñ©7Ìk‚>PTFpÔqu\Þ è÷©ã–ûþºÝ‰džï}Ó•¿Î©sNmçž[·ªI¸‚ÈK€ÉÀl` °0vR²X.¯Ù6cÙ`ޅוÅæX±i¦Qì¸:£“êcÔwyÌllÌløÈ˜xhUŒ^R£i¥!ISJW¦‹tLR¾Öq1rÆŸ§Tì4t¿DÀ…9.ñŠ´Ž|wéîCB!&8,óÈ¡,’Ò¿´2‘ëüBZÉ»ÐáuW#U™¹Ç^Ùnu·›{ÝÛw (3÷æmàdæ¾s#8™¹o] Nfîën'3÷¼…àdæž1œÌÜ“À!‹ò]ÿ™‰£lòµL­LåËà¥eðÒ2xi)|™Lô½"Çvo¤°Ûéõ )t„ºXèišÂB²P ­d¡Õ,4ž…f±‡…ì,”ËB^:ÈFÃ!æÝß§8Æ›ÁBÇXèqjc!7 °P> ©¬ÌåÎÈå# â7HG¥¼é@/+Çê“Êð¨1ïÄšpùI@7J^©y1ãÌ\Ió: ++bå¡cKUNäGPñ.Ã: (¸@GFGÐÈ4м˜ Î:`†u¾ÅÈS‘—Àl`p0Ã9pZâ“ÆÀJ⃞,KüR’“;½96»Íc›(¶`ߘË&çê¹¼ŒÒÓ±"§õ·ö²”ÎïRþñ] +%T&ðÍ| о5N·D¾ÏqDÙ=÷AGå öÊUul ¹Yèhj3Ê£Èn•t$Ùùc ¥ûtTK¸‹]¬Ÿ¬ÕéøÞþ±ãsìðÀ~f?èxK*,âø+$u:Þ°¯w¼TµBò´;Ê@ºTÃô€}´ãñc†éj(vF+%étÜnŸà¸Ön(ZbŠYm(ySSÜ3ÑžÏ>ÇámC›Ž +û,Çø˜Õ(Y§Ó1 CðÄØB vˆÝèÔ•k48­,ÊZ½E–í–FËdË¿YJ-E§Åaɱd[ZÓ¬6k?k²5Ñjµš­Š•[É:Pþª°G>õšmÆ×OŠÌƒ·q™Ë­‹ñ­€•c#¡ µ¼vj«ÕÏ¥Ú9ªöíTW”%ÖÏÐL®*¦¥ÕRmC•6ÚSµèS´2O­f©ûMc;c›j|]”QCc”éR´&[K«–Äú¯Ù”-é¥k6”‘~sEFEZyÿ15¾_É‚ñü¢¯Ø2úð9ÚöÚ©Ú¾œ€V*=Gb×˾fü¾ì+ID9ûÚ?EÊE¹/¨²é†v‚_Áó•agŃYÚ‘jÍÙíŒÙ >ìò%]Bv †Â¤]{[¾ßמŸoØ V©Í°i¬^ls¬6†MzˆŽ6ÇÒCÒF+7Lìv˜äÚ –EvÃÄβ “鿘”ÄMÖ÷š¬7zì{Ì&åLMÊØxþÕOKΗãs›ü-.Ðåo‚Ú†›[3´ÐUmŸ +Uîàœ¹­’6·hW‹O›ëò©íãš~EÝ$Õã\¾vjò74¶7y[|‘qÞq~W³/Ð1¡ndYŸ¾Ö÷ö5²îW«“”}M(ûu™TO}•ɾÊd_¼Œ¾ÈˆñºÆv+UpZ2hOJD¼³ªtÛâr#xÇ93Vfwa·²—’<-ÙU¥¥RU\Y\)U¸§¤ªÄ©qUÆÊqÎì.¶7®²AÜßUEž%KÛ–R†/öÓ†DK–J‡ÇrOÛÿõίy›}òýíµZ!Î~8ûµ[,å”´±=²¤$T?…p¬ +Ñk(eã¥,!!nø¿¯ÿÒ85Î’!~°ƒáLµ„ÚBË­màX +f`®M3»°—’‡¶&ØÆ<¬­§ø°{þŸ%É9÷`ÉÒ8÷Å’8ÕD•¶—ô~¤³<½[‚coƒ¯¦¯i<ý‘Œ7©ã¬4G–V%‡LÆò6éZ:DgY[Ê>ÁãÇÇçðb¹ø›ò£é3ó=–¥Ö µG³¬¤‰I{’÷¤¬è7±ß'©ëSÿÛVc‹Ú~NÛ6`þ€/U Úž^7xQÆÛ™G³ȾÍ>#GÉù)÷„c¢ã §êÜ•×éÚ‘kA“û÷¾KW± 9]øe±k訒撵%ïÈqZÊ»¯¢jýðä·ÚzÞcÿ˧Ú[=­¡Ò[Q~ÙøqcÇŒ.5rDéða%C‹‹<…C.½Ä]ïÊsªŽÜ{vV¦|Ö@ãEXýR’“Œÿ¢CpFE~WMPÕÜAMq»&N,–eW3Í ‚š +QM_M fj_K/,¯ù'KoÌÒÛkÉlêx_\¤ú]ªvÜçR£lF}#øM>W@ÕÎü$ƒßjð)àNTPý­>UcAÕ¯ÕÜÜö}h®=)±ÚUÝ’X\Dí‰I`“Àiƒ]‹ÛÙàrf0|°l;'k +¥e¹|~-Óå“#ÐD¿yžVWßè÷e;â"UÏuÍÑHÞ¡Īn4sµf1ºQÈÙе½èpxcÔFs‚žäy®yÍMšhÈ>ú{ЯO|ëÇ¿Ñ8nøµk³EØŸ±@•Åpx­ªÝ_ßx±Ö)ó@m ./¨ †kÐõF8±V.Þ_hÔØt©Ê™ÈYÅæ[â +‚ U-ÁUåj / âÒd…5šr‹3’•å= Ÿ¡,¿nht9µŠlW ÙgoHá)·tdzÕ̾šâ¢v[ÿ˜cÛû¥Æ™ä”‹™–^Áæ’«ÒëY&G亡©sUŒ¤Ñ…9–YËh +Ï 3| µ´y¸" ´„ê`Ø6VÊe}ÍT`s©áoà:÷÷¾’æ¸Ä\`û†$+ã¤7Ô ïá5G+,”!b©Æ5ÅËò¨â¢›£ÜåZlþMºêàÛæÀظßé”xCÔKsPÐBõ±²Js²#ä-ÁÂ̓Rs¸G3hšÔ„z4½Õƒ.Dò~ㆤYݽ?©¶ôþÖ±KÿÔ-1}ü¦U8÷mmCŸRL?ºWç´Õ"›Ç9ž- mleî1A¡1YS +ðc6‚z^ÔbET¦Öh¶àÄXHt:ÿÅJQý‚¬e_ªÅ‡©õô-ëSî3¼ä°À€7Ç%Nì£C¨Å:¼ÀŸãÏ…ûƒ=Õ»6dk5ñH¶²±¸)8Uµ»Øºúv/[7uFã¬ëê:ùNÆ«ƒUéHùõÅWϸ%ÅD¦.ʲL{(Sþ>‡|kð™¤Ý tù·€™’ò/9Ñ8ˆöÒãl=Ž'Úsìj=Ih?Éã»î£t7­Å‘l$ëi +’ ò»Y¦¾OÆp({€ŽÃöjZI]”Î2ôÏi­¯£ÖJ¡<ª¤:ZD›Ø•úRj¢ÓÊTFWÒ ´˜…ôF}³¾Mÿ=LÄŸõŸ)‰²h.ÒqýKÓÛúûTŒ¿§tšmKxмè%Ë?ÒM´SÌT˜>_ÿ#pÒ2ŒA¡Itœæ´ÞBŸ² ¶BT£•‡tMVvšI­´“ºØ(6;MMú$ý8¥£åhuE¨)JÏл,ÙtAÿ“~2©ˆ.Ç|öÓ vXtÿ¼º»‚äëTå«©Æ@³ˆþ‹ŽÒ«ÌÅžå‹LɦR“×t«þ ¤á4 £Ýƒšcßñ•H«Ä‹J^%ÿ×{úô6½@²,VÂ&³é|_Äw‰›ÈЇ#É—4¯§{Ðú)l:y2?)RS~4çtŸÑûኸé^ìQže)˜©ÊÚØoÙ›ì#^Ígó{ùYq·ò¨òš¥³žE×Ó&zŒ¾cil4«g¿a­l[Ë~Çv°ãìUö¯ä üZ~^´ŠÅ3JÒT¥M¹Ãt—iƒù³îÆîç»ÿÒý^ªßEõˆ‡Õýïifv€NÒ;H§±2±$ÖIeN6݆´’mb²½ìQ¶½¼ÊβÏqœú†ýȱâfž“<¾»øM|¿›ßÇO"½ÊÿοƒEžðˆQb¼ˆEÕZ±é)ñ¡’¥œTtø¹Ô´Ý´Û´×ô˜é9Ós²å·8Ÿ¾òÓC?þ|ª›º×uoïŽtï×?Ä^M~n'vtõÔŒ´×{;"îIz%ÃwY¬•³+á™Ùl!»‘-‡'ïd;ÙÃÆØŸ`OÃKo±ós +·cÊGñ*>ioá7ò­|ßÏßä?‹H©b(ÄLÑ"–ˆ[Äv¡‰WÄâ¬øVü„¤+‰ŠCÉSÜŠG™ ÌV–*»”O•OMM¦—MŸ˜Í×›ï2GÍ_áD^n©³Ô[fZ¶X:-oXƒˆÎ#ô”ñ½{…_NLbµl*-äÃc­™*û@Æ+Gèœò4æv-/7'³•ü¼9™"8ßAŸ/ˆaŠG¼LïŠÓÌ¢<@ï)‰l0;Ç÷ˆ:DÁ3J¹©‘œâ>zBÜÈn§§¸Ÿ(ñGëFÄñUlÖ…VÊþ!tü*DQ™øˆî kùÛt÷ñ:ú›§Ì§Í4‚­ OéÜCL7˜ ̓ØK|æØ~âÊ£˜Ý–Ï„i ÝÉfŠæóüZJ'•D:%þ£?ÉŸ“” ¦)¬wÀítݨ¯¦[LÊkl> 6 +”3XÝVˆRÅ º +«JÖ´NÜÝ]X*Å$H29W".¦a…؉tÖ ´÷øÕXÅNÐ~sÒ|S?†U'…—»§Ð ýڡϧômTŒõ`­¾-î¥Oh íekºo£Å”‹;ç»ÒTÃOšjôbæïð©|{ßë o° úé ÊM)¬¼ES©BߨÿÑ})VØ4‡® 1Ë/ÑÃDq˜Ft_ÅÛõ±ó=MõúÝÁ©U¿Ž&ÓÓô°ÅDÍ®±Æ^Ã|o£>E_"ZºÀ[à/¼µëÏzbÛŠÏjÚiÑdǨø ]ÃÓ¬&.@~àÊBý0-—ÿ6• ÇÝ0©Z¾7LÕ2½Þ]ÏFXÊYÄKL×uøÈyÈ_ûVÌ‹)@ãoÑ,¶–fð}´BBäò}à+A»¤ÒFÓ€ÓÀx`:—M𩲠Û²®ÕA‹LÓõŸMÓi»é(]ìÿ òí…þl·›÷Ñ=ß¹Ô5A7 4Á² ÏÑ6º¸ 'ï:Рõ€VkÙQZÇŽêBJw ­µRøât"Æ¿ú +ÔËGùðYèO~y– +8ùqºu¯û2³b +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/issue1466.pdf.link b/test/pdfs/issue1466.pdf.link new file mode 100644 index 000000000..22ebf7e2f --- /dev/null +++ b/test/pdfs/issue1466.pdf.link @@ -0,0 +1 @@ +http://web.missouri.edu/~fanxud/publications/On%20the%20performance%20quantification%20of%20resonant%20refractive%20index%20sensors.pdf diff --git a/test/pdfs/issue925.pdf b/test/pdfs/issue925.pdf new file mode 100755 index 000000000..16329cbef Binary files /dev/null and b/test/pdfs/issue925.pdf differ diff --git a/test/pdfs/pdfkit_compressed.pdf b/test/pdfs/pdfkit_compressed.pdf new file mode 100644 index 000000000..f3e25216d Binary files /dev/null and b/test/pdfs/pdfkit_compressed.pdf differ 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/resources/firefox/extensions/special-powers@mozilla.org/install.rdf b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf index db8de988e..490d4ddaf 100644 --- a/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf +++ b/test/resources/firefox/extensions/special-powers@mozilla.org/install.rdf @@ -14,7 +14,7 @@ toolkit@mozilla.org 3.0 - 7.0a1 + 13.0a1
diff --git a/test/test.py b/test/test.py index 84c6498ce..4dc6ca8a9 100644 --- a/test/test.py +++ b/test/test.py @@ -64,7 +64,9 @@ MIMEs = { '.svg': 'image/svg+xml', '.pdf': 'application/pdf', '.xhtml': 'application/xhtml+xml', + '.gif': 'image/gif', '.ico': 'image/x-icon', + '.png': 'image/png', '.log': 'text/plain' } diff --git a/test/test_manifest.json b/test/test_manifest.json index 5bc344abf..6a083bdf7 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -402,6 +402,27 @@ "link": true, "type": "eq" }, + { "id": "issue1096", + "file": "pdfs/issue1096.pdf", + "md5": "7f75d2b4b93c78d401ff39e8c1b00612", + "rounds": 1, + "pageLimit": 10, + "link": true, + "type": "eq" + }, + { "id": "issue1127", + "file": "pdfs/issue1127.pdf", + "md5": "4fb2be5ffefeafda4ba977de2a1bb4d8", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1249-load", + "file": "pdfs/issue1249.pdf", + "md5": "4f81339fa09422a7db980f34ea963609", + "rounds": 1, + "type": "load" + }, { "id": "liveprogramming", "file": "pdfs/liveprogramming.pdf", "md5": "7bd4dad1188232ef597d36fd72c33e52", @@ -423,11 +444,132 @@ "link": true, "type": "eq" }, + { "id": "issue1169", + "file": "pdfs/issue1169.pdf", + "md5": "3df3ed21fd43ac7fdb21e2015c8a7809", + "rounds": 1, + "link": true, + "type": "eq" + }, { "id": "zerowidthline", "file": "pdfs/zerowidthline.pdf", "md5": "295d26e61a85635433f8e4b768953f60", "rounds": 1, "link": false, "type": "eq" + }, + { "id": "html5checker", + "file": "pdfs/html5checker.pdf", + "md5": "74bbd80d1e7eb5f2951582233ef9ebab", + "rounds": 1, + "pageLimit": 7, + "link": true, + "type": "eq" + }, + { "id": "pdfkit_compressed", + "file": "pdfs/pdfkit_compressed.pdf", + "md5": "ffe9c571d0a1572e234253e6c7cdee6c", + "rounds": 1, + "type": "eq" + }, + { "id": "tamreview", + "file": "pdfs/TAMReview.pdf", + "md5": "8039aba56790d3597d2bc8c794a51301", + "rounds": 1, + "pageLimit": 5, + "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", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1466", + "file": "pdfs/issue1466.pdf", + "md5": "8a8877432e5bb10cfd50d60488d947bb", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1133", + "file": "pdfs/issue1133.pdf", + "md5": "d1b61580cb100e3df93d33703af1773a", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1049", + "file": "pdfs/issue1049.pdf", + "md5": "15473fffcdde9fb8f3756a4cf1aab347", + "rounds": 1, + "link": true, + "type": "eq" + }, + { "id": "issue1155", + "file": "pdfs/issue1155.pdf", + "md5": "b732ef25c16c9c20a77e40edef5aa6fe", + "rounds": 1, + "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", + "rounds": 1, + "pageLimit": 2, + "link": true, + "type": "eq" + }, + { "id": "issue1257", + "file": "pdfs/issue1257.pdf", + "md5": "9111533826bc21ed774e8e01603a2f54", + "rounds": 1, + "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" + }, + { "id": "gradientfill", + "file": "pdfs/gradientfill.pdf", + "md5": "cbc1988e4803f647fa83467a85f0e231", + "rounds": 1, + "type": "eq" } ] diff --git a/test/test_slave.html b/test/test_slave.html index a2682a5a6..50bb067e1 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -23,6 +23,7 @@ + + diff --git a/web/viewer.css b/web/viewer.css index 409a113c7..d042feab7 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -9,7 +9,7 @@ body { } [hidden] { - display: none; + display: none !important; } /* === Toolbar === */ @@ -65,16 +65,6 @@ body { line-height: 16px; } -span#info { - display: none; -} - -@-moz-document regexp("http:.*debug=1.*") { - span#info { - display: inline-block; - } -} - /* === Sidebar === */ #sidebar { position: fixed; @@ -89,7 +79,8 @@ span#info { z-index: 1; } -#sidebar:hover { +#sidebar:hover, +#sidebar.pinned { left: 0px; transition: left 0.25s ease-in-out 0s; -o-transition: left 0.25s ease-in-out 0s; @@ -97,6 +88,26 @@ span#info { -webkit-transition: left 0.25s ease-in-out 0s; } +#pinIcon { + position: absolute; + top: 4px; + right: 55px; + width: 15px; + height: 15px; + background: center no-repeat; + background-image: url('images/pin-up.svg'); + background-size: 15px 15px; +} + +#pinIcon:hover { + background-color: rgba(255,255,255,0.35); +} + +#sidebar.pinned #pinIcon { + background-image: url('images/pin-down.svg'); + background-size: 15px 15px; +} + #sidebarBox { background-color: rgba(0, 0, 0, 0.7); width: 300px; @@ -116,7 +127,7 @@ span#info { position: absolute; overflow: hidden; overflow-y: auto; - top: 10px; + top: 20px; bottom: 10px; left: 10px; width: 280px; @@ -147,7 +158,7 @@ span#info { position: absolute; background-color: #fff; overflow: auto; - top: 10px; + top: 20px; bottom: 10px; left: 10px; width: 280px; @@ -273,6 +284,16 @@ canvas { -webkit-box-shadow: 0px 2px 10px #ff0; } +.loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url('images/loading-icon.gif') center no-repeat; +} + .textLayer { position: absolute; left: 0; @@ -280,6 +301,7 @@ canvas { right: 0; bottom: 0; color: #000; + font-family: sans-serif; } .textLayer > div { @@ -407,7 +429,84 @@ canvas { } } -#loading { - margin: 100px 0; - text-align: center; +#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; + top: 35px; + bottom: 5px; + right: 2px; + width: 300px; + background: white; + border: 1px solid #666; + padding: 0; +} +#PDFBug .controls { + border-bottom: 1px solid #666; + padding: 3px; + background: -moz-linear-gradient(center bottom, #eee 0%, #fff 100%); +} +#PDFBug .panels { + overflow: auto; + position: absolute; + top: 27px; + left: 0; + right: 0; + bottom: 0; +} +#PDFBug button.active { + font-weight: bold; +} +.debuggerShowText { + background: yellow; + color: blue; + opacity: 0.3; +} +.debuggerHideText:hover { + background: yellow; + opacity: 0.3; +} +#PDFBug .stats { + font-size: 10px; + white-space: pre; + font-family: courier; +} +#PDFBug .stats .title { + font-weight: bold; } diff --git a/web/viewer.html b/web/viewer.html index 09c7195ce..9ec535db4 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -2,34 +2,40 @@ Simple pdf.js page viewer + + - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -91,13 +97,12 @@ -
+
Bookmark - -- -
Loading... 0%
+
+
Loading... 0%
+
+
diff --git a/web/viewer.js b/web/viewer.js index bc41e36d6..91639d9ee 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -6,6 +6,7 @@ var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf'; var kDefaultScale = 'auto'; var kDefaultScaleDelta = 1.1; +var kUnknownScale = 0; var kCacheSize = 20; var kCssUnits = 96.0 / 72.0; var kScrollbarPadding = 40; @@ -14,6 +15,15 @@ var kMaxScale = 4.0; var kImageDirectory = './images/'; var kSettingsMemory = 20; +function getFileName(url) { + var anchor = url.indexOf('#'); + var query = url.indexOf('?'); + var end = Math.min( + anchor > 0 ? anchor : url.length, + query > 0 ? query : url.length); + return url.substring(url.lastIndexOf('/', end) + 1, end); +} + var Cache = function cacheCache(size) { var data = []; this.push = function cachePush(view) { @@ -26,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 = []; @@ -36,10 +88,6 @@ var RenderingQueue = (function RenderingQueueClosure() { if (!item.drawingRequired()) return; // as no redraw required, no need for queueing. - if ('rendering' in item) - return; // is already in the queue - - item.rendering = true; this.items.push(item); if (this.items.length > 1) return; // not first item @@ -48,7 +96,6 @@ var RenderingQueue = (function RenderingQueueClosure() { }, continueExecution: function RenderingQueueContinueExecution() { var item = this.items.shift(); - delete item.rendering; if (this.items.length == 0) return; // queue is empty @@ -61,34 +108,54 @@ var RenderingQueue = (function RenderingQueueClosure() { return RenderingQueue; })(); +var FirefoxCom = (function FirefoxComClosure() { + return { + /** + * Creates an event that hopefully the extension is listening for and will + * synchronously respond to. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @return {*} The response. + */ + request: function(action, data) { + var request = document.createTextNode(''); + request.setUserData('action', action, null); + request.setUserData('data', data, null); + document.documentElement.appendChild(request); + + var sender = document.createEvent('Events'); + sender.initEvent('pdf.js.message', true, false); + request.dispatchEvent(sender); + var response = request.getUserData('response'); + document.documentElement.removeChild(request); + return response; + } + }; +})(); + // Settings Manager - This is a utility for saving settings -// First we see if localStorage is available, FF bug #495747 +// First we see if localStorage is available // If not, we use FUEL in FF var Settings = (function SettingsClosure() { var isLocalStorageEnabled = (function localStorageEnabledTest() { + // Feature test as per http://diveintohtml5.info/storage.html + // The additional localStorage call is to get around a FF quirk, see + // bug #495747 in bugzilla try { - localStorage; + return 'localStorage' in window && window['localStorage'] !== null && + localStorage; } catch (e) { return false; } - return true; })(); - var extPrefix = 'extensions.uriloader@pdf.js'; - var isExtension = location.protocol == 'chrome:' && !isLocalStorageEnabled; - var inPrivateBrowsing = false; - if (isExtension) { - var pbs = Components.classes['@mozilla.org/privatebrowsing;1'] - .getService(Components.interfaces.nsIPrivateBrowsingService); - inPrivateBrowsing = pbs.privateBrowsingEnabled; - } + + var isFirefoxExtension = PDFJS.isFirefoxExtension; function Settings(fingerprint) { var database = null; var index; - if (inPrivateBrowsing) - return false; - else if (isExtension) - database = Application.prefs.getValue(extPrefix + '.database', '{}'); + if (isFirefoxExtension) + database = FirefoxCom.request('getDatabase', null) || '{}'; else if (isLocalStorageEnabled) database = localStorage.getItem('database') || '{}'; else @@ -110,31 +177,27 @@ var Settings = (function SettingsClosure() { index = database.files.push({fingerprint: fingerprint}) - 1; this.file = database.files[index]; this.database = database; - if (isExtension) - Application.prefs.setValue(extPrefix + '.database', - JSON.stringify(database)); - else if (isLocalStorageEnabled) - localStorage.setItem('database', JSON.stringify(database)); } Settings.prototype = { set: function settingsSet(name, val) { - if (inPrivateBrowsing) + if (!('file' in this)) return false; + var file = this.file; file[name] = val; - if (isExtension) - Application.prefs.setValue(extPrefix + '.database', - JSON.stringify(this.database)); + var database = JSON.stringify(this.database); + if (isFirefoxExtension) + FirefoxCom.request('setDatabase', database); else if (isLocalStorageEnabled) - localStorage.setItem('database', JSON.stringify(this.database)); + localStorage.setItem('database', database); }, get: function settingsGet(name, defaultValue) { - if (inPrivateBrowsing) + if (!('file' in this)) return defaultValue; - else - return this.file[name] || defaultValue; + + return this.file[name] || defaultValue; } }; @@ -148,7 +211,7 @@ var currentPageNumber = 1; var PDFView = { pages: [], thumbnails: [], - currentScale: 0, + currentScale: kUnknownScale, currentScaleValue: null, initialBookmark: document.location.hash.substring(1), @@ -203,12 +266,12 @@ var PDFView = { zoomIn: function pdfViewZoomIn() { var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta); - this.setScale(newScale, true); + this.parseScale(newScale, true); }, zoomOut: function pdfViewZoomOut() { var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta); - this.setScale(newScale, true); + this.parseScale(newScale, true); }, set page(val) { @@ -222,6 +285,7 @@ var PDFView = { return; } + pages[val - 1].updateStats(); currentPageNumber = val; var event = document.createEvent('UIEvents'); event.initUIEvent('pagechange', false, false, window, 0); @@ -245,7 +309,13 @@ var PDFView = { }, open: function pdfViewOpen(url, scale) { - document.title = this.url = url; + this.url = url; + + document.title = decodeURIComponent(getFileName(url)) || url; + + if (!PDFView.loadingBar) { + PDFView.loadingBar = new ProgressBar('#loadingBar', {}); + } var self = this; PDFJS.getPdf( @@ -257,7 +327,7 @@ var PDFView = { }, error: function getPdfError(e) { var loadingIndicator = document.getElementById('loading'); - loadingIndicator.innerHTML = 'Error'; + loadingIndicator.textContent = 'Error'; var moreInfo = { message: 'Unexpected server response of ' + e.target.status + '.' }; @@ -272,7 +342,13 @@ var PDFView = { }, download: function pdfViewDownload() { - window.open(this.url + '#pdfjs.action=download', '_parent'); + var url = this.url.split('#')[0]; + if (PDFJS.isFirefoxExtension) { + FirefoxCom.request('download', url); + } else { + url += '#pdfjs.action=download', '_parent'; + window.open(url, '_parent'); + } }, navigateTo: function pdfViewNavigateTo(dest) { @@ -293,16 +369,17 @@ var PDFView = { getDestinationHash: function pdfViewGetDestinationHash(dest) { if (typeof dest === 'string') - return '#' + escape(dest); + return PDFView.getAnchorUrl('#' + escape(dest)); if (dest instanceof Array) { var destRef = dest[0]; // see navigateTo method for dest format var pageNumber = destRef instanceof Object ? this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); if (pageNumber) { - var pdfOpenParams = '#page=' + pageNumber; + var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); var destKind = dest[1]; - if ('name' in destKind && destKind.name == 'XYZ') { + if (typeof destKind === 'object' && 'name' in destKind && + destKind.name == 'XYZ') { var scale = (dest[4] || this.currentScale); pdfOpenParams += '&zoom=' + (scale * 100); if (dest[2] || dest[3]) { @@ -315,6 +392,17 @@ var PDFView = { return ''; }, + /** + * For the firefox extension we prefix the full url on anchor links so they + * don't come up as resource:// urls and so open in new tab/window works. + * @param {String} anchor The anchor hash include the #. + */ + getAnchorUrl: function getAnchorUrl(anchor) { + if (PDFJS.isFirefoxExtension) + return this.url.split('#')[0] + anchor; + return anchor; + }, + /** * Show the error box. * @param {String} message A message that is human readable. @@ -327,7 +415,7 @@ var PDFView = { errorWrapper.removeAttribute('hidden'); var errorMessage = document.getElementById('errorMessage'); - errorMessage.innerHTML = message; + errorMessage.textContent = message; var closeButton = document.getElementById('errorClose'); closeButton.onclick = function() { @@ -353,8 +441,14 @@ var PDFView = { if (moreInfo) { errorMoreInfo.value += 'Message: ' + moreInfo.message; - if (moreInfo.stack) + if (moreInfo.stack) { errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack; + } else { + if (moreInfo.filename) + errorMoreInfo.value += '\n' + 'File: ' + moreInfo.filename; + if (moreInfo.lineNumber) + errorMoreInfo.value += '\n' + 'Line: ' + moreInfo.lineNumber; + } } errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1; }, @@ -362,7 +456,9 @@ var PDFView = { progress: function pdfViewProgress(level) { var percent = Math.round(level * 100); var loadingIndicator = document.getElementById('loading'); - loadingIndicator.innerHTML = 'Loading... ' + percent + '%'; + loadingIndicator.textContent = 'Loading... ' + percent + '%'; + + PDFView.loadingBar.percent = percent; }, load: function pdfViewLoad(data, scale) { @@ -377,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; @@ -402,7 +498,7 @@ var PDFView = { var pagesCount = pdf.numPages; var id = pdf.fingerprint; var storedHash = null; - document.getElementById('numPages').innerHTML = pagesCount; + document.getElementById('numPages').textContent = pagesCount; document.getElementById('pageNumber').max = pagesCount; PDFView.documentFingerprint = id; var store = PDFView.store = new Settings(id); @@ -452,11 +548,35 @@ var PDFView = { } else if (storedHash) this.setHash(storedHash); - else { - this.parseScale(scale || kDefaultScale, true); + else if (scale) { + this.parseScale(scale, true); this.page = 1; } + if (PDFView.currentScale === kUnknownScale) { + // Scale was not initialized: invalid bookmark or scale was not specified. + // 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['Title']) + pdfTitle = info['Title']; + + if (pdfTitle) + document.title = pdfTitle + ' - ' + document.title; + // loosing pdf reference here, starting text indexing in 500ms setTimeout((function loadStartTextExtraction() { this.startTextExtraction(pdf); @@ -525,13 +645,7 @@ var PDFView = { return; if (hash.indexOf('=') >= 0) { - // parsing query string - var paramsPairs = hash.split('&'); - var params = {}; - for (var i = 0; i < paramsPairs.length; ++i) { - var paramPair = paramsPairs[i].split('='); - params[paramPair[0]] = paramPair[1]; - } + var params = PDFView.parseQueryString(hash); // borrowing syntax from "Parameters for Opening PDF Files" if ('nameddest' in params) { PDFView.navigateTo(params.nameddest); @@ -600,6 +714,10 @@ var PDFView = { } }, + pinSidebar: function pdfViewPinSidebar() { + document.getElementById('sidebar').classList.toggle('pinned'); + }, + getVisiblePages: function pdfViewGetVisiblePages() { var pages = this.pages; var kBottomMargin = 10; @@ -652,6 +770,19 @@ var PDFView = { } return visibleThumbs; + }, + + // Helper function to parse query string (e.g. ?param1=value&parm2=...). + parseQueryString: function pdfViewParseQueryString(query) { + var parts = query.split('&'); + var params = {}; + for (var i = 0, ii = parts.length; i < parts.length; ++i) { + var param = parts[i].split('='); + var key = param[0]; + var value = param.length > 1 ? param[1] : null; + params[unescape(key)] = unescape(value); + } + return params; } }; @@ -686,6 +817,10 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, div.removeAttribute('data-loaded'); delete this.canvas; + + this.loadingIconDiv = document.createElement('div'); + this.loadingIconDiv.className = 'loadingIcon'; + div.appendChild(this.loadingIconDiv); }; function setupAnnotations(content, scale) { @@ -723,7 +858,15 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (!item.content) { content.setAttribute('hidden', true); } else { - text.innerHTML = item.content.replace('\n', '
'); + var e = document.createElement('span'); + var lines = item.content.split('\n'); + for (var i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + e.appendChild(document.createTextNode(line)); + if (i < (ii - 1)) + e.appendChild(document.createElement('br')); + } + text.appendChild(e); image.addEventListener('mouseover', function annotationImageOver() { this.nextSibling.removeAttribute('hidden'); }, false); @@ -817,6 +960,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (scale && scale !== PDFView.currentScale) PDFView.parseScale(scale, true); + else if (PDFView.currentScale === kUnknownScale) + PDFView.parseScale(kDefaultScale, true); setTimeout(function pageViewScrollIntoViewRelayout() { // letting page to re-layout before scrolling @@ -840,7 +985,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, }; this.drawingRequired = function() { - return !div.hasChildNodes(); + return !div.querySelector('canvas'); }; this.draw = function pageviewDraw(callback) { @@ -875,35 +1020,42 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, ctx.restore(); ctx.translate(-this.x * scale, -this.y * scale); - stats.begin = Date.now(); - this.content.startRendering(ctx, - (function pageViewDrawCallback(error) { - if (error) - PDFView.error('An error occurred while rendering the page.', error); - this.updateStats(); - if (this.onAfterDraw) - this.onAfterDraw(); + // Rendering area - cache.push(this); - callback(); - }).bind(this), textLayer - ); + var self = this; + this.content.startRendering(ctx, function pageViewDrawCallback(error) { + if (self.loadingIconDiv) { + div.removeChild(self.loadingIconDiv); + delete self.loadingIconDiv; + } + + if (error) + PDFView.error('An error occurred while rendering the page.', error); + + self.stats = content.stats; + self.updateStats(); + if (self.onAfterDraw) + self.onAfterDraw(); + + cache.push(self); + callback(); + }, textLayer); setupAnnotations(this.content, this.scale); div.setAttribute('data-loaded', true); }; this.updateStats = function pageViewUpdateStats() { - var t1 = stats.compile, t2 = stats.fonts, t3 = stats.render; - var str = 'Time to compile/fonts/render: ' + - (t1 - stats.begin) + '/' + (t2 - t1) + '/' + (t3 - t2) + ' ms'; - document.getElementById('info').innerHTML = str; + if (PDFJS.pdfBug && Stats.enabled) { + var stats = this.stats; + Stats.add(this.id, stats); + } }; }; var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { var anchor = document.createElement('a'); - anchor.href = '#' + id; + anchor.href = PDFView.getAnchorUrl('#page=' + id); anchor.onclick = function stopNivigation() { PDFView.page = id; return false; @@ -976,7 +1128,7 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { }; this.setImage = function thumbnailViewSetImage(img) { - if (this.hasImage) + if (this.hasImage || !img) return; var ctx = getPageDrawContext(); @@ -1023,6 +1175,57 @@ var DocumentOutlineView = function documentOutlineView(outline) { } }; +// optimised CSS custom property getter/setter +var CustomStyle = (function CustomStyleClosure() { + + // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ + // animate-css-transforms-firefox-webkit.html + // in some versions of IE9 it is critical that ms appear in this list + // before Moz + var prefixes = ['ms', 'Moz', 'Webkit', 'O']; + var _cache = { }; + + function CustomStyle() { + } + + CustomStyle.getProp = function get(propName, element) { + // check cache only when no element is given + if (arguments.length == 1 && typeof _cache[propName] == 'string') { + return _cache[propName]; + } + + element = element || document.documentElement; + var style = element.style, prefixed, uPropName; + + // test standard property first + if (typeof style[propName] == 'string') { + return (_cache[propName] = propName); + } + + // capitalize + uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); + + // test vendor specific properties + for (var i = 0, l = prefixes.length; i < l; i++) { + prefixed = prefixes[i] + uPropName; + if (typeof style[prefixed] == 'string') { + return (_cache[propName] = prefixed); + } + } + + //if all fails then set to undefined + return (_cache[propName] = 'undefined'); + } + + CustomStyle.setProp = function set(propName, element, str) { + var prop = this.getProp(propName); + if (prop != 'undefined') + element.style[prop] = str; + } + + return CustomStyle; +})(); + var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { this.textLayerDiv = textLayerDiv; @@ -1052,12 +1255,13 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { textLayerDiv.appendChild(textDiv); if (textDiv.dataset.textLength > 1) { // avoid div by zero - // Adjust div width (via letterSpacing) to match canvas text + // Adjust div width to match canvas text // Due to the .offsetWidth calls, this is slow // This needs to come after appending to the DOM - textDiv.style.letterSpacing = - ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / - (textDiv.dataset.textLength - 1)) + 'px'; + var textScale = textDiv.dataset.canvasWidth / textDiv.offsetWidth; + CustomStyle.setProp('transform' , textDiv, + 'scale(' + textScale + ', 1)'); + CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); } } // textLength > 0 } @@ -1092,46 +1296,57 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { // vScale and hScale already contain the scaling to pixel units var fontHeight = fontSize * text.geom.vScale; textDiv.dataset.canvasWidth = text.canvasWidth * text.geom.hScale; + textDiv.dataset.fontName = fontName; textDiv.style.fontSize = fontHeight + 'px'; - textDiv.style.fontFamily = fontName || 'sans-serif'; textDiv.style.left = text.geom.x + 'px'; textDiv.style.top = (text.geom.y - fontHeight) + 'px'; - textDiv.textContent = text.str; + textDiv.textContent = PDFJS.bidi(text, -1); + textDiv.dir = text.direction; textDiv.dataset.textLength = text.length; this.textDivs.push(textDiv); }; }; window.addEventListener('load', function webViewerLoad(evt) { - var params = document.location.search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var param = params[i].split('='); - params[unescape(param[0])] = unescape(param[1]); + var params = PDFView.parseQueryString(document.location.search.substring(1)); + + var file = PDFJS.isFirefoxExtension ? + window.location.toString() : params.file || kDefaultURL; + PDFView.open(file, 0); + + if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader || + !window.FileList || !window.Blob) { + document.getElementById('fileInput').setAttribute('hidden', 'true'); + document.getElementById('fileInputSeperator') + .setAttribute('hidden', 'true'); + } else { + document.getElementById('fileInput').value = null; } - var scale = ('scale' in params) ? params.scale : 0; - PDFView.open(params.file || kDefaultURL, parseFloat(scale)); + // Special debugging flags in the hash section of the URL. + var hash = document.location.hash.substring(1); + var hashParams = PDFView.parseQueryString(hash); - if (!window.File || !window.FileReader || !window.FileList || !window.Blob) - document.getElementById('fileInput').setAttribute('hidden', 'true'); - else - document.getElementById('fileInput').value = null; + if ('disableWorker' in hashParams) + PDFJS.disableWorker = (hashParams['disableWorker'] === 'true'); - if ('disableWorker' in params) - PDFJS.disableWorker = (params['disableWorker'] === 'true'); + if ('disableTextLayer' in hashParams) + PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true'); - if ('disableTextLayer' in params) - PDFJS.disableTextLayer = (params['disableTextLayer'] === 'true'); + if ('pdfBug' in hashParams && + (!PDFJS.isFirefoxExtension || FirefoxCom.request('pdfBugEnabled'))) { + PDFJS.pdfBug = true; + var pdfBug = hashParams['pdfBug']; + var enabled = pdfBug.split(','); + PDFBug.enable(enabled); + PDFBug.init(); + } var sidebarScrollView = document.getElementById('sidebarScrollView'); 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. @@ -1201,15 +1416,14 @@ function updateViewarea() { store.set('zoom', normalizedScaleValue); store.set('scrollLeft', Math.round(topLeft.x)); store.set('scrollTop', Math.round(topLeft.y)); - - document.getElementById('viewBookmark').href = pdfOpenParams; + var href = PDFView.getAnchorUrl(pdfOpenParams); + document.getElementById('viewBookmark').href = href; } window.addEventListener('scroll', function webViewerScroll(evt) { updateViewarea(); }, true); - var thumbnailTimer; function updateThumbViewArea() { @@ -1342,7 +1556,7 @@ window.addEventListener('keydown', function keydown(evt) { handled = true; break; case 48: // '0' - PDFView.setScale(kDefaultScale, true); + PDFView.parseScale(kDefaultScale, true); handled = true; break; case 37: // left arrow