aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--3rdparty/Google/LICENSE.txt21
-rwxr-xr-x3rdparty/Google/OAuth.php751
-rwxr-xr-x3rdparty/Google/common.inc.php185
-rw-r--r--apps/bookmarks/appinfo/database.xml2
-rw-r--r--apps/calendar/appinfo/database.xml4
-rw-r--r--apps/calendar/appinfo/version2
-rw-r--r--apps/calendar/templates/calendar.php38
-rw-r--r--apps/contacts/ajax/activation.php1
-rw-r--r--apps/contacts/ajax/addcontact.php1
-rw-r--r--apps/contacts/ajax/addproperty.php1
-rw-r--r--apps/contacts/ajax/createaddressbook.php1
-rw-r--r--apps/contacts/ajax/cropphoto.php2
-rw-r--r--apps/contacts/ajax/deletebook.php1
-rw-r--r--apps/contacts/ajax/deletecard.php11
-rw-r--r--apps/contacts/ajax/deleteproperty.php1
-rw-r--r--apps/contacts/ajax/savecrop.php1
-rw-r--r--apps/contacts/ajax/saveproperty.php9
-rw-r--r--apps/contacts/ajax/uploadphoto.php2
-rw-r--r--apps/contacts/import.php2
-rw-r--r--apps/contacts/js/contacts.js16
-rw-r--r--apps/contacts/lib/vcard.php1
-rw-r--r--apps/contacts/templates/part.contact.php2
-rw-r--r--apps/contacts/templates/part.cropphoto.php2
-rw-r--r--apps/contacts/templates/part.importaddressbook.php5
-rw-r--r--apps/files/js/fileactions.js8
-rw-r--r--apps/files/js/files.js2
-rw-r--r--apps/files_external/ajax/dropbox.php41
-rw-r--r--apps/files_external/ajax/google.php51
-rw-r--r--apps/files_external/js/dropbox.js53
-rw-r--r--apps/files_external/js/google.js48
-rw-r--r--apps/files_external/js/settings.js86
-rwxr-xr-xapps/files_external/lib/config.php4
-rw-r--r--apps/files_external/lib/google.php2
-rw-r--r--apps/files_external/templates/settings.php3
-rw-r--r--apps/files_pdfviewer/appinfo/app.php3
-rw-r--r--apps/files_pdfviewer/appinfo/info.xml2
-rw-r--r--apps/files_pdfviewer/css/viewer.css1
-rw-r--r--apps/files_pdfviewer/js/pdfjs/build/pdf.js2820
-rw-r--r--apps/files_pdfviewer/js/pdfjs/compatibility.js340
-rwxr-xr-xapps/files_pdfviewer/js/pdfjs/update.sh3
-rw-r--r--apps/files_pdfviewer/js/pdfjs/viewer.js1936
-rw-r--r--apps/files_pdfviewer/js/viewer.js19
-rw-r--r--apps/files_sharing/appinfo/database.xml2
-rw-r--r--apps/files_sharing/lib_share.php8
-rw-r--r--apps/gallery/appinfo/database.xml2
-rw-r--r--apps/gallery/lib/managers.php2
-rw-r--r--apps/media/appinfo/database.xml2
-rw-r--r--apps/remoteStorage/appinfo/database.xml2
-rw-r--r--apps/tasks/js/tasks.js7
-rw-r--r--core/lostpassword/index.php2
-rw-r--r--core/templates/layout.user.php10
-rw-r--r--lib/config.php6
-rw-r--r--lib/filesystem.php3
-rw-r--r--lib/json.php12
-rw-r--r--lib/public/json.php7
-rw-r--r--lib/template.php4
-rwxr-xr-xlib/util.php45
-rw-r--r--settings/css/settings.css2
-rw-r--r--settings/js/users.js12
-rw-r--r--settings/templates/users.php110
61 files changed, 5893 insertions, 832 deletions
diff --git a/.gitignore b/.gitignore
index ac58f3e6a63..e2ff07d14d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,9 @@ nbproject
# Cloud9IDE
.settings.xml
+# vim ex mode
+.vimrc
+
# Mac OS
.DS_Store
diff --git a/3rdparty/Google/LICENSE.txt b/3rdparty/Google/LICENSE.txt
new file mode 100644
index 00000000000..8891c7ddc90
--- /dev/null
+++ b/3rdparty/Google/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/3rdparty/Google/OAuth.php b/3rdparty/Google/OAuth.php
new file mode 100755
index 00000000000..c7e75dd8266
--- /dev/null
+++ b/3rdparty/Google/OAuth.php
@@ -0,0 +1,751 @@
+<?php
+// vim: foldmethod=marker
+
+/* Generic exception class
+ */
+class OAuthException extends Exception {/*{{{*/
+ // pass
+}/*}}}*/
+
+class OAuthConsumer {/*{{{*/
+ public $key;
+ public $secret;
+
+ public function __construct($key, $secret, $callback_url=NULL) {/*{{{*/
+ $this->key = $key;
+ $this->secret = $secret;
+ $this->callback_url = $callback_url;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthToken {/*{{{*/
+ // access tokens and request tokens
+ public $key;
+ public $secret;
+
+ /**
+ * key = the token
+ * secret = the token secret
+ */
+ function __construct($key, $secret) {/*{{{*/
+ $this->key = $key;
+ $this->secret = $secret;
+ }/*}}}*/
+
+ /**
+ * generates the basic string serialization of a token that a server
+ * would respond to request_token and access_token calls with
+ */
+ function to_string() {/*{{{*/
+ return "oauth_token=" . OAuthUtil::urlencodeRFC3986($this->key) .
+ "&oauth_token_secret=" . OAuthUtil::urlencodeRFC3986($this->secret);
+ }/*}}}*/
+
+ function __toString() {/*{{{*/
+ return $this->to_string();
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthSignatureMethod {/*{{{*/
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $built = $this->build_signature($request, $consumer, $token);
+ return $built == $signature;
+ }
+}/*}}}*/
+
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/
+ function get_name() {/*{{{*/
+ return "HMAC-SHA1";
+ }/*}}}*/
+
+ public function build_signature($request, $consumer, $token, $privKey=NULL) {/*{{{*/
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ $key_parts = array(
+ $consumer->secret,
+ ($token) ? $token->secret : ""
+ );
+
+ $key_parts = array_map(array('OAuthUtil','urlencodeRFC3986'), $key_parts);
+ $key = implode('&', $key_parts);
+
+ return base64_encode( hash_hmac('sha1', $base_string, $key, true));
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/
+ public function get_name() {/*{{{*/
+ return "RSA-SHA1";
+ }/*}}}*/
+
+ protected function fetch_public_cert(&$request) {/*{{{*/
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ // (2) fetch via http using a url provided by the requester
+ // (3) some sort of specific discovery code based on request
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_public_cert not implemented");
+ }/*}}}*/
+
+ protected function fetch_private_cert($privKey) {//&$request) {/*{{{*/
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_private_cert not implemented");
+ }/*}}}*/
+
+ public function build_signature(&$request, $consumer, $token, $privKey) {/*{{{*/
+ $base_string = $request->get_signature_base_string();
+
+ // Fetch the private key cert based on the request
+ //$cert = $this->fetch_private_cert($consumer->privKey);
+
+ //Pull the private key ID from the certificate
+ //$privatekeyid = openssl_get_privatekey($cert);
+
+ // hacked in
+ if ($privKey == '') {
+ $fp = fopen($GLOBALS['PRIV_KEY_FILE'], "r");
+ $privKey = fread($fp, 8192);
+ fclose($fp);
+ }
+ $privatekeyid = openssl_get_privatekey($privKey);
+
+ //Check the computer signature against the one passed in the query
+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+ //Release the key resource
+ openssl_free_key($privatekeyid);
+
+ return base64_encode($signature);
+ } /*}}}*/
+
+ public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/
+ $decoded_sig = base64_decode($signature);
+
+ $base_string = $request->get_signature_base_string();
+
+ // Fetch the public key cert based on the request
+ $cert = $this->fetch_public_cert($request);
+
+ //Pull the public key ID from the certificate
+ $publickeyid = openssl_get_publickey($cert);
+
+ //Check the computer signature against the one passed in the query
+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+ //Release the key resource
+ openssl_free_key($publickeyid);
+
+ return $ok == 1;
+ } /*}}}*/
+}/*}}}*/
+
+class OAuthRequest {/*{{{*/
+ private $parameters;
+ private $http_method;
+ private $http_url;
+ // for debug purposes
+ public $base_string;
+ public static $version = '1.0';
+
+ function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/
+ @$parameters or $parameters = array();
+ $this->parameters = $parameters;
+ $this->http_method = $http_method;
+ $this->http_url = $http_url;
+ }/*}}}*/
+
+
+ /**
+ * attempt to build up a request from what was passed to the server
+ */
+ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/
+ $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https';
+ @$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
+
+ $request_headers = OAuthRequest::get_headers();
+
+ // let the library user override things however they'd like, if they know
+ // which parameters to use then go for it, for example XMLRPC might want to
+ // do this
+ if ($parameters) {
+ $req = new OAuthRequest($http_method, $http_url, $parameters);
+ }
+ // next check for the auth header, we need to do some extra stuff
+ // if that is the case, namely suck in the parameters from GET or POST
+ // so that we can include them in the signature
+ else if (@substr($request_headers['Authorization'], 0, 5) == "OAuth") {
+ $header_parameters = OAuthRequest::split_header($request_headers['Authorization']);
+ if ($http_method == "GET") {
+ $req_parameters = $_GET;
+ }
+ else if ($http_method = "POST") {
+ $req_parameters = $_POST;
+ }
+ $parameters = array_merge($header_parameters, $req_parameters);
+ $req = new OAuthRequest($http_method, $http_url, $parameters);
+ }
+ else if ($http_method == "GET") {
+ $req = new OAuthRequest($http_method, $http_url, $_GET);
+ }
+ else if ($http_method == "POST") {
+ $req = new OAuthRequest($http_method, $http_url, $_POST);
+ }
+ return $req;
+ }/*}}}*/
+
+ /**
+ * pretty much a helper function to set up the request
+ */
+ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/
+ @$parameters or $parameters = array();
+ $defaults = array("oauth_version" => OAuthRequest::$version,
+ "oauth_nonce" => OAuthRequest::generate_nonce(),
+ "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+ "oauth_consumer_key" => $consumer->key);
+ $parameters = array_merge($defaults, $parameters);
+
+ if ($token) {
+ $parameters['oauth_token'] = $token->key;
+ }
+
+ // oauth v1.0a
+ /*if (isset($_REQUEST['oauth_verifier'])) {
+ $parameters['oauth_verifier'] = $_REQUEST['oauth_verifier'];
+ }*/
+
+
+ return new OAuthRequest($http_method, $http_url, $parameters);
+ }/*}}}*/
+
+ public function set_parameter($name, $value) {/*{{{*/
+ $this->parameters[$name] = $value;
+ }/*}}}*/
+
+ public function get_parameter($name) {/*{{{*/
+ return $this->parameters[$name];
+ }/*}}}*/
+
+ public function get_parameters() {/*{{{*/
+ return $this->parameters;
+ }/*}}}*/
+
+ /**
+ * Returns the normalized parameters of the request
+ *
+ * This will be all (except oauth_signature) parameters,
+ * sorted first by key, and if duplicate keys, then by
+ * value.
+ *
+ * The returned string will be all the key=value pairs
+ * concated by &.
+ *
+ * @return string
+ */
+ public function get_signable_parameters() {/*{{{*/
+ // Grab all parameters
+ $params = $this->parameters;
+
+ // Remove oauth_signature if present
+ if (isset($params['oauth_signature'])) {
+ unset($params['oauth_signature']);
+ }
+
+ // Urlencode both keys and values
+ $keys = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_keys($params));
+ $values = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_values($params));
+ $params = array_combine($keys, $values);
+
+ // Sort by keys (natsort)
+ uksort($params, 'strnatcmp');
+
+if(isset($params['title']) && isset($params['title-exact'])) {
+ $temp = $params['title-exact'];
+ $title = $params['title'];
+
+ unset($params['title']);
+ unset($params['title-exact']);
+
+ $params['title-exact'] = $temp;
+ $params['title'] = $title;
+}
+
+ // Generate key=value pairs
+ $pairs = array();
+ foreach ($params as $key=>$value ) {
+ if (is_array($value)) {
+ // If the value is an array, it's because there are multiple
+ // with the same key, sort them, then add all the pairs
+ natsort($value);
+ foreach ($value as $v2) {
+ $pairs[] = $key . '=' . $v2;
+ }
+ } else {
+ $pairs[] = $key . '=' . $value;
+ }
+ }
+
+ // Return the pairs, concated with &
+ return implode('&', $pairs);
+ }/*}}}*/
+
+ /**
+ * Returns the base string of this request
+ *
+ * The base string defined as the method, the url
+ * and the parameters (normalized), each urlencoded
+ * and the concated with &.
+ */
+ public function get_signature_base_string() {/*{{{*/
+ $parts = array(
+ $this->get_normalized_http_method(),
+ $this->get_normalized_http_url(),
+ $this->get_signable_parameters()
+ );
+
+ $parts = array_map(array('OAuthUtil', 'urlencodeRFC3986'), $parts);
+
+ return implode('&', $parts);
+ }/*}}}*/
+
+ /**
+ * just uppercases the http method
+ */
+ public function get_normalized_http_method() {/*{{{*/
+ return strtoupper($this->http_method);
+ }/*}}}*/
+
+/**
+ * parses the url and rebuilds it to be
+ * scheme://host/path
+ */
+ public function get_normalized_http_url() {
+ $parts = parse_url($this->http_url);
+
+ $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
+ $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
+ $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
+ $path = (isset($parts['path'])) ? $parts['path'] : '';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ return "$scheme://$host$path";
+ }
+
+ /**
+ * builds a url usable for a GET request
+ */
+ public function to_url() {/*{{{*/
+ $out = $this->get_normalized_http_url() . "?";
+ $out .= $this->to_postdata();
+ return $out;
+ }/*}}}*/
+
+ /**
+ * builds the data one would send in a POST request
+ */
+ public function to_postdata() {/*{{{*/
+ $total = array();
+ foreach ($this->parameters as $k => $v) {
+ $total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v);
+ }
+ $out = implode("&", $total);
+ return $out;
+ }/*}}}*/
+
+ /**
+ * builds the Authorization: header
+ */
+ public function to_header() {/*{{{*/
+ $out ='Authorization: OAuth ';
+ $total = array();
+
+ /*
+ $sig = $this->parameters['oauth_signature'];
+ unset($this->parameters['oauth_signature']);
+ uksort($this->parameters, 'strnatcmp');
+ $this->parameters['oauth_signature'] = $sig;
+ */
+
+ foreach ($this->parameters as $k => $v) {
+ if (substr($k, 0, 5) != "oauth") continue;
+ $out .= OAuthUtil::urlencodeRFC3986($k) . '="' . OAuthUtil::urlencodeRFC3986($v) . '", ';
+ }
+ $out = substr_replace($out, '', strlen($out) - 2);
+
+ return $out;
+ }/*}}}*/
+
+ public function __toString() {/*{{{*/
+ return $this->to_url();
+ }/*}}}*/
+
+
+ public function sign_request($signature_method, $consumer, $token, $privKey=NULL) {/*{{{*/
+ $this->set_parameter("oauth_signature_method", $signature_method->get_name());
+ $signature = $this->build_signature($signature_method, $consumer, $token, $privKey);
+ $this->set_parameter("oauth_signature", $signature);
+ }/*}}}*/
+
+ public function build_signature($signature_method, $consumer, $token, $privKey=NULL) {/*{{{*/
+ $signature = $signature_method->build_signature($this, $consumer, $token, $privKey);
+ return $signature;
+ }/*}}}*/
+
+ /**
+ * util function: current timestamp
+ */
+ private static function generate_timestamp() {/*{{{*/
+ return time();
+ }/*}}}*/
+
+ /**
+ * util function: current nonce
+ */
+ private static function generate_nonce() {/*{{{*/
+ $mt = microtime();
+ $rand = mt_rand();
+
+ return md5($mt . $rand); // md5s look nicer than numbers
+ }/*}}}*/
+
+ /**
+ * util function for turning the Authorization: header into
+ * parameters, has to do some unescaping
+ */
+ private static function split_header($header) {/*{{{*/
+ // this should be a regex
+ // error cases: commas in parameter values
+ $parts = explode(",", $header);
+ $out = array();
+ foreach ($parts as $param) {
+ $param = ltrim($param);
+ // skip the "realm" param, nobody ever uses it anyway
+ if (substr($param, 0, 5) != "oauth") continue;
+
+ $param_parts = explode("=", $param);
+
+ // rawurldecode() used because urldecode() will turn a "+" in the
+ // value into a space
+ $out[$param_parts[0]] = rawurldecode(substr($param_parts[1], 1, -1));
+ }
+ return $out;
+ }/*}}}*/
+
+ /**
+ * helper to try to sort out headers for people who aren't running apache
+ */
+ private static function get_headers() {/*{{{*/
+ if (function_exists('apache_request_headers')) {
+ // we need this to get the actual Authorization: header
+ // because apache tends to tell us it doesn't exist
+ return apache_request_headers();
+ }
+ // otherwise we don't have apache and are just going to have to hope
+ // that $_SERVER actually contains what we need
+ $out = array();
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) == "HTTP_") {
+ // this is chaos, basically it is just there to capitalize the first
+ // letter of every word that is not an initial HTTP and strip HTTP
+ // code from przemek
+ $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
+ $out[$key] = $value;
+ }
+ }
+ return $out;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthServer {/*{{{*/
+ protected $timestamp_threshold = 300; // in seconds, five minutes
+ protected $version = 1.0; // hi blaine
+ protected $signature_methods = array();
+
+ protected $data_store;
+
+ function __construct($data_store) {/*{{{*/
+ $this->data_store = $data_store;
+ }/*}}}*/
+
+ public function add_signature_method($signature_method) {/*{{{*/
+ $this->signature_methods[$signature_method->get_name()] =
+ $signature_method;
+ }/*}}}*/
+
+ // high level functions
+
+ /**
+ * process a request_token request
+ * returns the request token on success
+ */
+ public function fetch_request_token(&$request) {/*{{{*/
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // no token required for the initial token request
+ $token = NULL;
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_request_token($consumer);
+
+ return $new_token;
+ }/*}}}*/
+
+ /**
+ * process an access_token request
+ * returns the access token on success
+ */
+ public function fetch_access_token(&$request) {/*{{{*/
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // requires authorized request token
+ $token = $this->get_token($request, $consumer, "request");
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_access_token($token, $consumer);
+
+ return $new_token;
+ }/*}}}*/
+
+ /**
+ * verify an api call, checks all the parameters
+ */
+ public function verify_request(&$request) {/*{{{*/
+ $this->get_version($request);
+ $consumer = $this->get_consumer($request);
+ $token = $this->get_token($request, $consumer, "access");
+ $this->check_signature($request, $consumer, $token);
+ return array($consumer, $token);
+ }/*}}}*/
+
+ // Internals from here
+ /**
+ * version 1
+ */
+ private function get_version(&$request) {/*{{{*/
+ $version = $request->get_parameter("oauth_version");
+ if (!$version) {
+ $version = 1.0;
+ }
+ if ($version && $version != $this->version) {
+ throw new OAuthException("OAuth version '$version' not supported");
+ }
+ return $version;
+ }/*}}}*/
+
+ /**
+ * figure out the signature with some defaults
+ */
+ private function get_signature_method(&$request) {/*{{{*/
+ $signature_method =
+ @$request->get_parameter("oauth_signature_method");
+ if (!$signature_method) {
+ $signature_method = "PLAINTEXT";
+ }
+ if (!in_array($signature_method,
+ array_keys($this->signature_methods))) {
+ throw new OAuthException(
+ "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods))
+ );
+ }
+ return $this->signature_methods[$signature_method];
+ }/*}}}*/
+
+ /**
+ * try to find the consumer for the provided request's consumer key
+ */
+ private function get_consumer(&$request) {/*{{{*/
+ $consumer_key = @$request->get_parameter("oauth_consumer_key");
+ if (!$consumer_key) {
+ throw new OAuthException("Invalid consumer key");
+ }
+
+ $consumer = $this->data_store->lookup_consumer($consumer_key);
+ if (!$consumer) {
+ throw new OAuthException("Invalid consumer");
+ }
+
+ return $consumer;
+ }/*}}}*/
+
+ /**
+ * try to find the token for the provided request's token key
+ */
+ private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/
+ $token_field = @$request->get_parameter('oauth_token');
+ $token = $this->data_store->lookup_token(
+ $consumer, $token_type, $token_field
+ );
+ if (!$token) {
+ throw new OAuthException("Invalid $token_type token: $token_field");
+ }
+ return $token;
+ }/*}}}*/
+
+ /**
+ * all-in-one function to check the signature on a request
+ * should guess the signature method appropriately
+ */
+ private function check_signature(&$request, $consumer, $token) {/*{{{*/
+ // this should probably be in a different method
+ $timestamp = @$request->get_parameter('oauth_timestamp');
+ $nonce = @$request->get_parameter('oauth_nonce');
+
+ $this->check_timestamp($timestamp);
+ $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+ $signature_method = $this->get_signature_method($request);
+
+ $signature = $request->get_parameter('oauth_signature');
+ $valid_sig = $signature_method->check_signature(
+ $request,
+ $consumer,
+ $token,
+ $signature
+ );
+
+ if (!$valid_sig) {
+ throw new OAuthException("Invalid signature");
+ }
+ }/*}}}*/
+
+ /**
+ * check that the timestamp is new enough
+ */
+ private function check_timestamp($timestamp) {/*{{{*/
+ // verify that timestamp is recentish
+ $now = time();
+ if ($now - $timestamp > $this->timestamp_threshold) {
+ throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
+ }
+ }/*}}}*/
+
+ /**
+ * check that the nonce is not repeated
+ */
+ private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ // verify that the nonce is uniqueish
+ $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
+ if ($found) {
+ throw new OAuthException("Nonce already used: $nonce");
+ }
+ }/*}}}*/
+
+
+
+}/*}}}*/
+
+class OAuthDataStore {/*{{{*/
+ function lookup_consumer($consumer_key) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function lookup_token($consumer, $token_type, $token) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function fetch_request_token($consumer) {/*{{{*/
+ // return a new token attached to this consumer
+ }/*}}}*/
+
+ function fetch_access_token($token, $consumer) {/*{{{*/
+ // return a new access token attached to this consumer
+ // for the user associated with this token if the request token
+ // is authorized
+ // should also invalidate the request token
+ }/*}}}*/
+
+}/*}}}*/
+
+
+/* A very naive dbm-based oauth storage
+ */
+class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
+ private $dbh;
+
+ function __construct($path = "oauth.gdbm") {/*{{{*/
+ $this->dbh = dba_popen($path, 'c', 'gdbm');
+ }/*}}}*/
+
+ function __destruct() {/*{{{*/
+ dba_close($this->dbh);
+ }/*}}}*/
+
+ function lookup_consumer($consumer_key) {/*{{{*/
+ $rv = dba_fetch("consumer_$consumer_key", $this->dbh);
+ if ($rv === FALSE) {
+ return NULL;
+ }
+ $obj = unserialize($rv);
+ if (!($obj instanceof OAuthConsumer)) {
+ return NULL;
+ }
+ return $obj;
+ }/*}}}*/
+
+ function lookup_token($consumer, $token_type, $token) {/*{{{*/
+ $rv = dba_fetch("${token_type}_${token}", $this->dbh);
+ if ($rv === FALSE) {
+ return NULL;
+ }
+ $obj = unserialize($rv);
+ if (!($obj instanceof OAuthToken)) {
+ return NULL;
+ }
+ return $obj;
+ }/*}}}*/
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ return dba_exists("nonce_$nonce", $this->dbh);
+ }/*}}}*/
+
+ function new_token($consumer, $type="request") {/*{{{*/
+ $key = md5(time());
+ $secret = time() + time();
+ $token = new OAuthToken($key, md5(md5($secret)));
+ if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
+ throw new OAuthException("doooom!");
+ }
+ return $token;
+ }/*}}}*/
+
+ function new_request_token($consumer) {/*{{{*/
+ return $this->new_token($consumer, "request");
+ }/*}}}*/
+
+ function new_access_token($token, $consumer) {/*{{{*/
+
+ $token = $this->new_token($consumer, 'access');
+ dba_delete("request_" . $token->key, $this->dbh);
+ return $token;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthUtil {/*{{{*/
+ public static function urlencodeRFC3986($string) {/*{{{*/
+ return str_replace('%7E', '~', rawurlencode($string));
+ }/*}}}*/
+
+ public static function urldecodeRFC3986($string) {/*{{{*/
+ return rawurldecode($string);
+ }/*}}}*/
+}/*}}}*/
+
+?> \ No newline at end of file
diff --git a/3rdparty/Google/common.inc.php b/3rdparty/Google/common.inc.php
new file mode 100755
index 00000000000..57185cdc4d8
--- /dev/null
+++ b/3rdparty/Google/common.inc.php
@@ -0,0 +1,185 @@
+<?php
+/* Copyright (c) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Eric Bidelman <e.bidelman@google.com>
+ */
+
+$PRIV_KEY_FILE = '/path/to/your/rsa_private_key.pem';
+
+// OAuth library - http://oauth.googlecode.com/svn/code/php/
+require_once('OAuth.php');
+
+// Google's accepted signature methods
+$hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+$rsa_method = new OAuthSignatureMethod_RSA_SHA1();
+$SIG_METHODS = array($rsa_method->get_name() => $rsa_method,
+ $hmac_method->get_name() => $hmac_method);
+
+/**
+ * Makes an HTTP request to the specified URL
+ *
+ * @param string $http_method The HTTP method (GET, POST, PUT, DELETE)
+ * @param string $url Full URL of the resource to access
+ * @param array $extraHeaders (optional) Additional headers to include in each
+ * request. Elements are header/value pair strings ('Host: example.com')
+ * @param string $postData (optional) POST/PUT request body
+ * @param bool $returnResponseHeaders True if resp. headers should be returned.
+ * @return string Response body from the server
+ */
+function send_signed_request($http_method, $url, $extraHeaders=null,
+ $postData=null, $returnResponseHeaders=true) {
+ $curl = curl_init($url);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_FAILONERROR, false);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+
+ // Return request headers in the reponse
+// curl_setopt($curl, CURLINFO_HEADER_OUT, true);
+
+ // Return response headers ni the response?
+ if ($returnResponseHeaders) {
+ curl_setopt($curl, CURLOPT_HEADER, true);
+ }
+
+ $headers = array();
+ //$headers[] = 'GData-Version: 2.0'; // use GData v2 by default
+ if (is_array($extraHeaders)) {
+ $headers = array_merge($headers, $extraHeaders);
+ }
+
+ // Setup default curl options for each type of HTTP request.
+ // This is also a great place to add additional headers for each request.
+ switch($http_method) {
+ case 'GET':
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ break;
+ case 'POST':
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($curl, CURLOPT_POST, 1);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
+ break;
+ case 'PUT':
+ $headers[] = 'If-Match: *';
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
+ break;
+ case 'DELETE':
+ $headers[] = 'If-Match: *';
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $http_method);
+ break;
+ default:
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ }
+
+ // Execute the request. If an error occures, fill the response body with it.
+ $response = curl_exec($curl);
+ if (!$response) {
+ $response = curl_error($curl);
+ }
+
+ // Add server's response headers to our response body
+ $response = curl_getinfo($curl, CURLINFO_HEADER_OUT) . $response;
+
+ curl_close($curl);
+
+ return $response;
+}
+
+/**
+* Takes XML as a string and returns it nicely indented
+*
+* @param string $xml The xml to beautify
+* @param boolean $html_output True if returned XML should be escaped for HTML.
+* @return string The beautified xml
+*/
+function xml_pretty_printer($xml, $html_output=false) {
+ $xml_obj = new SimpleXMLElement($xml);
+ $level = 2;
+
+ // Get an array containing each XML element
+ $xml = explode("\n", preg_replace('/>\s*</', ">\n<", $xml_obj->asXML()));
+
+ // Hold current indentation level
+ $indent = 0;
+
+ $pretty = array();
+
+ // Shift off opening XML tag if present
+ if (count($xml) && preg_match('/^<\?\s*xml/', $xml[0])) {
+ $pretty[] = array_shift($xml);
+ }
+
+ foreach ($xml as $el) {
+ if (preg_match('/^<([\w])+[^>\/]*>$/U', $el)) {
+ // opening tag, increase indent
+ $pretty[] = str_repeat(' ', $indent) . $el;
+ $indent += $level;
+ } else {
+ if (preg_match('/^<\/.+>$/', $el)) {
+ $indent -= $level; // closing tag, decrease indent
+ }
+ if ($indent < 0) {
+ $indent += $level;
+ }
+ $pretty[] = str_repeat(' ', $indent) . $el;
+ }
+ }
+
+ $xml = implode("\n", $pretty);
+ return $html_output ? htmlentities($xml) : $xml;
+}
+
+/**
+ * Joins key/value pairs by $inner_glue and each pair together by $outer_glue.
+ *
+ * Example: implode_assoc('=', '&', array('a' => 1, 'b' => 2)) === 'a=1&b=2'
+ *
+ * @param string $inner_glue What to implode each key/value pair with
+ * @param string $outer_glue What to impode each key/value string subset with
+ * @param array $array Associative array of query parameters
+ * @return string Urlencoded string of query parameters
+ */
+function implode_assoc($inner_glue, $outer_glue, $array) {
+ $output = array();
+ foreach($array as $key => $item) {
+ $output[] = $key . $inner_glue . urlencode($item);
+ }
+ return implode($outer_glue, $output);
+}
+
+/**
+ * Explodes a string of key/value url parameters into an associative array.
+ * This method performs the compliment operations of implode_assoc().
+ *
+ * Example: explode_assoc('=', '&', 'a=1&b=2') === array('a' => 1, 'b' => 2)
+ *
+ * @param string $inner_glue What each key/value pair is joined with
+ * @param string $outer_glue What each set of key/value pairs is joined with.
+ * @param array $array Associative array of query parameters
+ * @return array Urlencoded string of query parameters
+ */
+function explode_assoc($inner_glue, $outer_glue, $params) {
+ $tempArr = explode($outer_glue, $params);
+ foreach($tempArr as $val) {
+ $pos = strpos($val, $inner_glue);
+ $key = substr($val, 0, $pos);
+ $array2[$key] = substr($val, $pos + 1, strlen($val));
+ }
+ return $array2;
+}
+
+?> \ No newline at end of file
diff --git a/apps/bookmarks/appinfo/database.xml b/apps/bookmarks/appinfo/database.xml
index f2fc68e4b58..b03c1fb2c89 100644
--- a/apps/bookmarks/appinfo/database.xml
+++ b/apps/bookmarks/appinfo/database.xml
@@ -3,7 +3,7 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
<name>*dbprefix*bookmarks</name>
<declaration>
diff --git a/apps/calendar/appinfo/database.xml b/apps/calendar/appinfo/database.xml
index b065ab3f94a..5a3ad32dc24 100644
--- a/apps/calendar/appinfo/database.xml
+++ b/apps/calendar/appinfo/database.xml
@@ -81,7 +81,7 @@
<type>text</type>
<default></default>
<notnull>false</notnull>
- <length>100</length>
+ <length>255</length>
</field>
<field>
@@ -133,7 +133,7 @@
<type>text</type>
<default></default>
<notnull>false</notnull>
- <length>100</length>
+ <length>255</length>
</field>
<field>
diff --git a/apps/calendar/appinfo/version b/apps/calendar/appinfo/version
index e6adf3fc7bb..267577d47e4 100644
--- a/apps/calendar/appinfo/version
+++ b/apps/calendar/appinfo/version
@@ -1 +1 @@
-0.4 \ No newline at end of file
+0.4.1
diff --git a/apps/calendar/templates/calendar.php b/apps/calendar/templates/calendar.php
index 832194f0fe1..b0cb20f2f15 100644
--- a/apps/calendar/templates/calendar.php
+++ b/apps/calendar/templates/calendar.php
@@ -34,29 +34,21 @@
});
</script>
<div id="controls">
- <div>
- <form>
- <div id="view">
- <input type="button" value="<?php echo $l->t('Week');?>" id="oneweekview_radio"/>
- <input type="button" value="<?php echo $l->t('Month');?>" id="onemonthview_radio"/>
- <input type="button" value="<?php echo $l->t('List');?>" id="listview_radio"/>&nbsp;&nbsp;
- <img id="loading" src="<?php echo OCP\Util::imagePath('core', 'loading.gif'); ?>" />
- </div>
- </form>
- <form>
- <div id="choosecalendar">
- <input type="button" id="today_input" value="<?php echo $l->t("Today");?>"/>
- <input type="button" id="choosecalendar_input" value="<?php echo $l->t("Calendars");?>" onclick="Calendar.UI.Calendar.overview();" />
- </div>
- </form>
- <form>
- <div id="datecontrol">
- <input type="button" value="&nbsp;&lt;&nbsp;" id="datecontrol_left"/>
- <span class="button" id="datecontrol_date"></span>
- <input type="button" value="&nbsp;&gt;&nbsp;" id="datecontrol_right"/>
- </div>
- </form>
- </div>
+ <form id="view">
+ <input type="button" value="<?php echo $l->t('Week');?>" id="oneweekview_radio"/>
+ <input type="button" value="<?php echo $l->t('Month');?>" id="onemonthview_radio"/>
+ <input type="button" value="<?php echo $l->t('List');?>" id="listview_radio"/>&nbsp;&nbsp;
+ <img id="loading" src="<?php echo OCP\Util::imagePath('core', 'loading.gif'); ?>" />
+ </form>
+ <form id="choosecalendar">
+ <input type="button" id="today_input" value="<?php echo $l->t("Today");?>"/>
+ <input type="button" id="choosecalendar_input" value="<?php echo $l->t("Calendars");?>" onclick="Calendar.UI.Calendar.overview();" />
+ </form>
+ <form id="datecontrol">
+ <input type="button" value="&nbsp;&lt;&nbsp;" id="datecontrol_left"/>
+ <span class="button" id="datecontrol_date"></span>
+ <input type="button" value="&nbsp;&gt;&nbsp;" id="datecontrol_right"/>
+ </form>
</div>
<div id="notification" style="display:none;"></div>
<div id="calendar_holder">
diff --git a/apps/contacts/ajax/activation.php b/apps/contacts/ajax/activation.php
index 388a3b5438c..74cb738ab8f 100644
--- a/apps/contacts/ajax/activation.php
+++ b/apps/contacts/ajax/activation.php
@@ -10,6 +10,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
$bookid = $_POST['bookid'];
$book = OC_Contacts_App::getAddressbook($bookid);// is owner access check
diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php
index af9b2bbcc0e..e45072c9542 100644
--- a/apps/contacts/ajax/addcontact.php
+++ b/apps/contacts/ajax/addcontact.php
@@ -23,6 +23,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
$aid = isset($_POST['aid'])?$_POST['aid']:null;
if(!$aid) {
diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php
index 94e09bac190..f888b94e386 100644
--- a/apps/contacts/ajax/addproperty.php
+++ b/apps/contacts/ajax/addproperty.php
@@ -23,6 +23,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
function bailOut($msg) {
OCP\JSON::error(array('data' => array('message' => $msg)));
diff --git a/apps/contacts/ajax/createaddressbook.php b/apps/contacts/ajax/createaddressbook.php
index af7c19eef51..616766bb1a0 100644
--- a/apps/contacts/ajax/createaddressbook.php
+++ b/apps/contacts/ajax/createaddressbook.php
@@ -11,6 +11,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
$userid = OCP\USER::getUser();
$name = trim(strip_tags($_POST['name']));
diff --git a/apps/contacts/ajax/cropphoto.php b/apps/contacts/ajax/cropphoto.php
index caba7c8c4ef..eb9f1fcdb5d 100644
--- a/apps/contacts/ajax/cropphoto.php
+++ b/apps/contacts/ajax/cropphoto.php
@@ -25,10 +25,12 @@ OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
$tmpkey = $_GET['tmpkey'];
+$requesttoken = $_GET['requesttoken'];
$id = $_GET['id'];
$tmpl = new OCP\Template("contacts", "part.cropphoto");
$tmpl->assign('tmpkey', $tmpkey);
$tmpl->assign('id', $id);
+$tmpl->assign('requesttoken', $requesttoken);
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/contacts/ajax/deletebook.php b/apps/contacts/ajax/deletebook.php
index fe582daa00f..1b86ecf223e 100644
--- a/apps/contacts/ajax/deletebook.php
+++ b/apps/contacts/ajax/deletebook.php
@@ -23,6 +23,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
//$id = $_GET['id'];
$id = $_POST['id'];
diff --git a/apps/contacts/ajax/deletecard.php b/apps/contacts/ajax/deletecard.php
index e6d0405a240..2a6bd277d19 100644
--- a/apps/contacts/ajax/deletecard.php
+++ b/apps/contacts/ajax/deletecard.php
@@ -28,6 +28,17 @@ function bailOut($msg) {
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
+
+// foreach($_SERVER as $key=>$value) {
+// OCP\Util::writeLog('contacts','ajax/saveproperty.php: _SERVER: '.$key.'=>'.$value, OCP\Util::DEBUG);
+// }
+foreach($_POST as $key=>$value) {
+ OCP\Util::writeLog('contacts','ajax/saveproperty.php: _POST: '.$key.'=>'.print_r($value, true), OCP\Util::DEBUG);
+}
+foreach($_GET as $key=>$value) {
+ OCP\Util::writeLog('contacts','ajax/saveproperty.php: _GET: '.$key.'=>'.print_r($value, true), OCP\Util::DEBUG);
+}
$id = isset($_POST['id'])?$_POST['id']:null;
if(!$id) {
diff --git a/apps/contacts/ajax/deleteproperty.php b/apps/contacts/ajax/deleteproperty.php
index e6c2bd9f803..55f7e323083 100644
--- a/apps/contacts/ajax/deleteproperty.php
+++ b/apps/contacts/ajax/deleteproperty.php
@@ -23,6 +23,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
$id = $_POST['id'];
$checksum = $_POST['checksum'];
diff --git a/apps/contacts/ajax/savecrop.php b/apps/contacts/ajax/savecrop.php
index b3aab6a8810..6faf6a173d5 100644
--- a/apps/contacts/ajax/savecrop.php
+++ b/apps/contacts/ajax/savecrop.php
@@ -22,6 +22,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain');
diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php
index d8400734710..6ee9ec90b56 100644
--- a/apps/contacts/ajax/saveproperty.php
+++ b/apps/contacts/ajax/saveproperty.php
@@ -20,10 +20,6 @@
*
*/
-// Check if we are a user
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('contacts');
-
function bailOut($msg) {
OCP\JSON::error(array('data' => array('message' => $msg)));
OCP\Util::writeLog('contacts','ajax/saveproperty.php: '.$msg, OCP\Util::DEBUG);
@@ -33,6 +29,11 @@ function debug($msg) {
OCP\Util::writeLog('contacts','ajax/saveproperty.php: '.$msg, OCP\Util::DEBUG);
}
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
+
$id = isset($_POST['id'])?$_POST['id']:null;
$name = isset($_POST['name'])?$_POST['name']:null;
$value = isset($_POST['value'])?$_POST['value']:null;
diff --git a/apps/contacts/ajax/uploadphoto.php b/apps/contacts/ajax/uploadphoto.php
index 32abc6c2859..889de6a1f8b 100644
--- a/apps/contacts/ajax/uploadphoto.php
+++ b/apps/contacts/ajax/uploadphoto.php
@@ -23,6 +23,8 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
+
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain');
function bailOut($msg) {
diff --git a/apps/contacts/import.php b/apps/contacts/import.php
index 0ee35f9fd81..c95fd970fe1 100644
--- a/apps/contacts/import.php
+++ b/apps/contacts/import.php
@@ -15,7 +15,7 @@ session_write_close();
$nl = "\n";
global $progresskey;
-$progresskey = 'contacts.import-' . $_GET['progresskey'];
+$progresskey = 'contacts.import-' . (isset($_GET['progresskey'])?$_GET['progresskey']:'');
if (isset($_GET['progress']) && $_GET['progress']) {
echo OC_Cache::get($progresskey);
diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js
index a241856300b..45509a7f9db 100644
--- a/apps/contacts/js/contacts.js
+++ b/apps/contacts/js/contacts.js
@@ -622,7 +622,7 @@ Contacts={
q = q + '&id=' + this.id + '&name=' + name;
if(checksum != undefined && checksum != '') { // save
q = q + '&checksum=' + checksum;
- //console.log('Saving: ' + q);
+ console.log('Saving: ' + q);
$(obj).attr('disabled', 'disabled');
$.post(OC.filePath('contacts', 'ajax', 'saveproperty.php'),q,function(jsondata){
if(jsondata.status == 'success'){
@@ -640,7 +640,7 @@ Contacts={
}
},'json');
} else { // add
- //console.log('Adding: ' + q);
+ console.log('Adding: ' + q);
$(obj).attr('disabled', 'disabled');
$.post(OC.filePath('contacts', 'ajax', 'addproperty.php'),q,function(jsondata){
if(jsondata.status == 'success'){
@@ -839,22 +839,22 @@ Contacts={
$('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
var adrarray = this.data.ADR[adr]['value'];
var adrtxt = '';
- if(adrarray[0].length > 0) {
+ if(adrarray[0] && adrarray[0].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[0].strip_tags() + '</li>';
}
- if(adrarray[1].length > 0) {
+ if(adrarray[1] && adrarray[1].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[1].strip_tags() + '</li>';
}
- if(adrarray[2].length > 0) {
+ if(adrarray[2] && adrarray[2].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[2].strip_tags() + '</li>';
}
- if(adrarray[3].length > 0 || adrarray[5].length > 0) {
+ if((adrarray[3] && adrarray[5]) && adrarray[3].length > 0 || adrarray[5].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[5].strip_tags() + ' ' + adrarray[3].strip_tags() + '</li>';
}
- if(adrarray[4].length > 0) {
+ if(adrarray[4] && adrarray[4].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[4].strip_tags() + '</li>';
}
- if(adrarray[6].length > 0) {
+ if(adrarray[6] && adrarray[6].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[6].strip_tags() + '</li>';
}
$('#addressdisplay dl').last().find('.addresslist').html(adrtxt);
diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php
index 20a9e4afc9c..71a874d783b 100644
--- a/apps/contacts/lib/vcard.php
+++ b/apps/contacts/lib/vcard.php
@@ -188,6 +188,7 @@ class OC_Contacts_VCard{
if($upgrade && in_array($property->name, $stringprops)) {
self::decodeProperty($property);
}
+ $property->value = str_replace("\r\n", "\n", iconv(mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), 'utf-8', $property->value));
if(in_array($property->name, $stringprops)) {
$property->value = strip_tags($property->value);
}
diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php
index c1ba1ccdc21..ca682baaf80 100644
--- a/apps/contacts/templates/part.contact.php
+++ b/apps/contacts/templates/part.contact.php
@@ -3,6 +3,7 @@ $id = isset($_['id']) ? $_['id'] : '';
?>
<div id="card">
<form class="float" id="file_upload_form" action="<?php echo OCP\Util::linkTo('contacts', 'ajax/uploadphoto.php'); ?>" method="post" enctype="multipart/form-data" target="file_upload_target">
+ <input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>">
<input type="hidden" name="id" value="<?php echo $_['id'] ?>">
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $_['uploadMaxFilesize'] ?>" id="max_upload">
<input type="hidden" class="max_human_file_size" value="(max <?php echo $_['uploadMaxHumanFilesize']; ?>)">
@@ -23,6 +24,7 @@ $id = isset($_['id']) ? $_['id'] : '';
<div id="contact_identity" class="contactsection">
<form method="post">
<input type="hidden" name="id" value="<?php echo $_['id'] ?>">
+ <input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>">
<fieldset id="ident" class="contactpart">
<span class="propertycontainer" data-element="N"><input type="hidden" id="n" class="contacts_property" name="value" value="" /></span>
<span id="name" class="propertycontainer" data-element="FN">
diff --git a/apps/contacts/templates/part.cropphoto.php b/apps/contacts/templates/part.cropphoto.php
index d7f0efc57d7..1e025ef4e0c 100644
--- a/apps/contacts/templates/part.cropphoto.php
+++ b/apps/contacts/templates/part.cropphoto.php
@@ -1,6 +1,7 @@
<?php
$id = $_['id'];
$tmpkey = $_['tmpkey'];
+$csrf_token = $_GET['csrf_token'];
OCP\Util::writeLog('contacts','templates/part.cropphoto.php: tmpkey: '.$tmpkey, OCP\Util::DEBUG);
?>
<script language="Javascript">
@@ -48,6 +49,7 @@ OCP\Util::writeLog('contacts','templates/part.cropphoto.php: tmpkey: '.$tmpkey,
action="<?php echo OCP\Util::linkToAbsolute('contacts', 'ajax/savecrop.php'); ?>">
<input type="hidden" id="id" name="id" value="<?php echo $id; ?>" />
+ <input type="hidden" name="requesttoken" value="<?php echo $csrf_token; ?>">
<input type="hidden" id="tmpkey" name="tmpkey" value="<?php echo $tmpkey; ?>" />
<fieldset id="coords">
<input type="hidden" id="x1" name="x1" value="" />
diff --git a/apps/contacts/templates/part.importaddressbook.php b/apps/contacts/templates/part.importaddressbook.php
index 0e2956ddaf4..01f8dd77d0a 100644
--- a/apps/contacts/templates/part.importaddressbook.php
+++ b/apps/contacts/templates/part.importaddressbook.php
@@ -7,10 +7,6 @@
*/
?>
<td id="importaddressbook_dialog" colspan="6">
-<?php
-if(OCP\App::isEnabled('files_encryption')) {
- echo '<strong>'.$l->t('Currently this import function doesn\'t work while encryption is enabled.<br />Please upload your VCF file with the file manager and click on it to import.').'</strong>';
-} else { ?>
<table>
<tr>
<th><?php echo $l->t('Select address book to import to:') ?></th>
@@ -33,7 +29,6 @@ if(OCP\App::isEnabled('files_encryption')) {
<input id="close_button" style="float: left;" type="button" onclick="Contacts.UI.Addressbooks.cancel(this);" value="<?php echo $l->t("Cancel"); ?>">
<iframe name="import_upload_target" id='import_upload_target' src=""></iframe>
-<?php } ?>
</td>
<script type="text/javascript">
$(document).ready(function(){
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 0c672cd6708..deec640bc12 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -71,7 +71,7 @@ FileActions={
}
var html='<a href="#" class="action" style="display:none">';
if(img) { html+='<img src="'+img+'"/> '; }
- html += name+'</a>';
+ html += t('files', name) +'</a>';
var element=$(html);
element.data('action',name);
element.click(function(event){
@@ -91,7 +91,11 @@ FileActions={
if(img.call){
img=img(file);
}
- var html='<a href="#" original-title="Delete" class="action delete" style="display:none" />';
+ if ($('#dir').val().indexOf('Shared') != -1) {
+ var html='<a href="#" original-title="' + t('files', 'Unshare') + '" class="action delete" style="display:none" />';
+ } else {
+ var html='<a href="#" original-title="' + t('files', 'Delete') + '" class="action delete" style="display:none" />';
+ }
var element=$(html);
if(img){
element.append($('<img src="'+img+'"/>'));
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index a079deb9539..3ba473e023d 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -452,7 +452,7 @@ $(document).ready(function() {
input.focus();
input.change(function(){
var name=$(this).val();
- if(name.indexOf('/')!=-1){
+ if(type != 'web' && name.indexOf('/')!=-1){
$('#notification').text(t('files','Invalid name, \'/\' is not allowed.'));
$('#notification').fadeIn();
return;
diff --git a/apps/files_external/ajax/dropbox.php b/apps/files_external/ajax/dropbox.php
new file mode 100644
index 00000000000..5f2ff17e625
--- /dev/null
+++ b/apps/files_external/ajax/dropbox.php
@@ -0,0 +1,41 @@
+<?php
+
+require_once 'Dropbox/autoload.php';
+
+OCP\JSON::checkAppEnabled('files_external');
+OCP\JSON::checkLoggedIn();
+if (isset($_POST['app_key']) && isset($_POST['app_secret'])) {
+ $oauth = new Dropbox_OAuth_Curl($_POST['app_key'], $_POST['app_secret']);
+ if (isset($_POST['step'])) {
+ switch ($_POST['step']) {
+ case 1:
+ try {
+ if (isset($_POST['callback'])) {
+ $callback = $_POST['callback'];
+ } else {
+ $callback = null;
+ }
+ $token = $oauth->getRequestToken();
+ OCP\JSON::success(array('data' => array('url' => $oauth->getAuthorizeUrl($callback), 'request_token' => $token['token'], 'request_token_secret' => $token['token_secret'])));
+ } catch (Exception $exception) {
+ OCP\JSON::error(array('data' => array('message' => 'Fetching request tokens failed. Verify that your Dropbox app key and secret are correct.')));
+ }
+ break;
+ case 2:
+ if (isset($_POST['request_token']) && isset($_POST['request_token_secret'])) {
+ try {
+ $oauth->setToken($_POST['request_token'], $_POST['request_token_secret']);
+ $token = $oauth->getAccessToken();
+ OCP\JSON::success(array('access_token' => $token['token'], 'access_token_secret' => $token['token_secret']));
+ } catch (Exception $exception) {
+ OCP\JSON::error(array('data' => array('message' => 'Fetching access tokens failed. Verify that your Dropbox app key and secret are correct.')));
+ }
+ }
+ break;
+ }
+ }
+} else {
+ OCP\JSON::error(array('data' => array('message' => 'Please provide a valid Dropbox app key and secret.')));
+}
+
+?> \ No newline at end of file
diff --git a/apps/files_external/ajax/google.php b/apps/files_external/ajax/google.php
new file mode 100644
index 00000000000..23ecfc3708d
--- /dev/null
+++ b/apps/files_external/ajax/google.php
@@ -0,0 +1,51 @@
+<?php
+
+require_once 'Google/common.inc.php';
+
+OCP\JSON::checkAppEnabled('files_external');
+OCP\JSON::checkLoggedIn();
+$consumer = new OAuthConsumer('anonymous', 'anonymous');
+$sigMethod = new OAuthSignatureMethod_HMAC_SHA1();
+if (isset($_POST['step'])) {
+ switch ($_POST['step']) {
+ case 1:
+ if (isset($_POST['callback'])) {
+ $callback = $_POST['callback'];
+ } else {
+ $callback = null;
+ }
+ $scope = 'https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/';
+ $url = 'https://www.google.com/accounts/OAuthGetRequestToken?scope='.urlencode($scope);
+ $params = array('scope' => $scope, 'oauth_callback' => $callback);
+ $request = OAuthRequest::from_consumer_and_token($consumer, null, 'GET', $url, $params);
+ $request->sign_request($sigMethod, $consumer, null);
+ $response = send_signed_request('GET', $url, array($request->to_header()), null, false);
+ $token = array();
+ parse_str($response, $token);
+ if (isset($token['oauth_token']) && isset($token['oauth_token_secret'])) {
+ $authUrl = 'https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token='.$token['oauth_token'];
+ OCP\JSON::success(array('data' => array('url' => $authUrl, 'request_token' => $token['oauth_token'], 'request_token_secret' => $token['oauth_token_secret'])));
+ } else {
+ OCP\JSON::error(array('data' => array('message' => 'Fetching request tokens failed. Error: '.$response)));
+ }
+ break;
+ case 2:
+ if (isset($_POST['oauth_verifier']) && isset($_POST['request_token']) && isset($_POST['request_token_secret'])) {
+ $token = new OAuthToken($_POST['request_token'], $_POST['request_token_secret']);
+ $url = 'https://www.google.com/accounts/OAuthGetAccessToken';
+ $request = OAuthRequest::from_consumer_and_token($consumer, $token, 'GET', $url, array('oauth_verifier' => $_POST['oauth_verifier']));
+ $request->sign_request($sigMethod, $consumer, $token);
+ $response = send_signed_request('GET', $url, array($request->to_header()), null, false);
+ $token = array();
+ parse_str($response, $token);
+ if (isset($token['oauth_token']) && isset($token['oauth_token_secret'])) {
+ OCP\JSON::success(array('access_token' => $token['oauth_token'], 'access_token_secret' => $token['oauth_token_secret']));
+ } else {
+ OCP\JSON::error(array('data' => array('message' => 'Fetching access tokens failed. Error: '.$response)));
+ }
+ }
+ break;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/apps/files_external/js/dropbox.js b/apps/files_external/js/dropbox.js
new file mode 100644
index 00000000000..67f3c46a6ed
--- /dev/null
+++ b/apps/files_external/js/dropbox.js
@@ -0,0 +1,53 @@
+$(document).ready(function() {
+
+ $('#externalStorage tbody tr').each(function() {
+ if ($(this).find('.backend').data('class') == 'OC_Filestorage_Dropbox') {
+ var app_key = $(this).find('.configuration [data-parameter="app_key"]').val();
+ var app_secret = $(this).find('.configuration [data-parameter="app_secret"]').val();
+ if (app_key == '' && app_secret == '') {
+ $(this).find('.configuration').append('<a class="button dropbox">Grant access</a>');
+ } else {
+ var pos = window.location.search.indexOf('oauth_token') + 12
+ var token = $(this).find('.configuration [data-parameter="token"]');
+ if (pos != -1 && window.location.search.substr(pos, $(token).val().length) == $(token).val()) {
+ var token_secret = $(this).find('.configuration [data-parameter="token_secret"]');
+ var tr = $(this);
+ $.post(OC.filePath('files_external', 'ajax', 'dropbox.php'), { step: 2, app_key: app_key, app_secret: app_secret, request_token: $(token).val(), request_token_secret: $(token_secret).val() }, function(result) {
+ if (result && result.status == 'success') {
+ $(token).val(result.access_token);
+ $(token_secret).val(result.access_token_secret);
+ OC.MountConfig.saveStorage(tr);
+ } else {
+ OC.dialogs.alert(result.data.message, 'Error configuring Dropbox storage');
+ }
+ });
+ }
+ }
+ return false;
+ }
+ });
+
+ $('.dropbox').live('click', function(event) {
+ event.preventDefault();
+ var app_key = $(this).parent().find('[data-parameter="app_key"]').val();
+ var app_secret = $(this).parent().find('[data-parameter="app_secret"]').val();
+ if (app_key != '' && app_secret != '') {
+ var tr = $(this).parent().parent();
+ var token = $(this).parent().find('[data-parameter="token"]');
+ var token_secret = $(this).parent().find('[data-parameter="token_secret"]');
+ $.post(OC.filePath('files_external', 'ajax', 'dropbox.php'), { step: 1, app_key: app_key, app_secret: app_secret, callback: window.location.href }, function(result) {
+ if (result && result.status == 'success') {
+ $(token).val(result.data.request_token);
+ $(token_secret).val(result.data.request_token_secret);
+ OC.MountConfig.saveStorage(tr);
+ window.location = result.data.url;
+ } else {
+ OC.dialogs.alert(result.data.message, 'Error configuring Dropbox storage');
+ }
+ });
+ } else {
+ OC.dialogs.alert('Please provide a valid Dropbox app key and secret.', 'Error configuring Dropbox storage')
+ }
+ });
+
+});
diff --git a/apps/files_external/js/google.js b/apps/files_external/js/google.js
new file mode 100644
index 00000000000..0d65cfda011
--- /dev/null
+++ b/apps/files_external/js/google.js
@@ -0,0 +1,48 @@
+$(document).ready(function() {
+
+ $('#externalStorage tbody tr').each(function() {
+ if ($(this).find('.backend').data('class') == 'OC_Filestorage_Google') {
+ var token = $(this).find('[data-parameter="token"]');
+ var token_secret = $(this).find('[data-parameter="token_secret"]');
+ if ($(token).val() == '' && $(token).val() == '') {
+ $(this).find('.configuration').append('<a class="button google">Grant access</a>');
+ } else {
+ var params = {};
+ window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
+ params[key] = value;
+ });
+ if (params['oauth_token'].length > 1 && decodeURIComponent(params['oauth_token']) == $(token).val() && params['oauth_verifier'].length > 1) {
+ var tr = $(this);
+ $.post(OC.filePath('files_external', 'ajax', 'google.php'), { step: 2, oauth_verifier: params['oauth_verifier'], request_token: $(token).val(), request_token_secret: $(token_secret).val() }, function(result) {
+ if (result && result.status == 'success') {
+ $(token).val(result.access_token);
+ $(token_secret).val(result.access_token_secret);
+ OC.MountConfig.saveStorage(tr);
+ } else {
+ OC.dialogs.alert(result.data.message, 'Error configuring Google Drive storage');
+ }
+ });
+ }
+ }
+ return false;
+ }
+ });
+
+ $('.google').live('click', function(event) {
+ event.preventDefault();
+ var tr = $(this).parent().parent();
+ var token = $(this).parent().find('[data-parameter="token"]');
+ var token_secret = $(this).parent().find('[data-parameter="token_secret"]');
+ $.post(OC.filePath('files_external', 'ajax', 'google.php'), { step: 1, callback: window.location.href }, function(result) {
+ if (result && result.status == 'success') {
+ $(token).val(result.data.request_token);
+ $(token_secret).val(result.data.request_token_secret);
+ OC.MountConfig.saveStorage(tr);
+ window.location = result.data.url;
+ } else {
+ OC.dialogs.alert(result.data.message, 'Error configuring Google Drive storage');
+ }
+ });
+ });
+
+});
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js
index 38291d5f7e2..57188a6a266 100644
--- a/apps/files_external/js/settings.js
+++ b/apps/files_external/js/settings.js
@@ -1,40 +1,5 @@
-$(document).ready(function() {
-
- $('.chzn-select').chosen();
-
- $('#selectBackend').live('change', function() {
- var tr = $(this).parent().parent();
- $('#externalStorage tbody').last().append($(tr).clone());
- var selected = $(this).find('option:selected').text();
- var backendClass = $(this).val();
- $(this).parent().text(selected);
- $(tr).find('.backend').data('class', $(this).val());
- var configurations = $(this).data('configurations');
- var td = $(tr).find('td.configuration');
- $.each(configurations, function(backend, parameters) {
- if (backend == backendClass) {
- $.each(parameters['configuration'], function(parameter, placeholder) {
- if (placeholder.indexOf('*') != -1) {
- td.append('<input type="password" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
- } else if (placeholder.indexOf('!') != -1) {
- td.append('<label><input type="checkbox" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
- } else if (placeholder.indexOf('&') != -1) {
- td.append('<input type="text" class="optional" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
- } else {
- td.append('<input type="text" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
- }
- });
- return false;
- }
- });
- $('.chz-select').chosen();
- $(tr).find('td').last().attr('class', 'remove');
- $(tr).removeAttr('id');
- $(this).remove();
- });
-
- $('#externalStorage td').live('change', function() {
- var tr = $(this).parent();
+OC.MountConfig={
+ saveStorage:function(tr) {
var mountPoint = $(tr).find('.mountPoint input').val();
if (mountPoint == '') {
return false;
@@ -99,6 +64,51 @@ $(document).ready(function() {
$.post(OC.filePath('files_external', 'ajax', 'addMountPoint.php'), { mountPoint: mountPoint, class: backendClass, classOptions: classOptions, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
}
}
+ }
+}
+
+$(document).ready(function() {
+
+ $('.chzn-select').chosen();
+
+ $('#selectBackend').live('change', function() {
+ var tr = $(this).parent().parent();
+ $('#externalStorage tbody').last().append($(tr).clone());
+ var selected = $(this).find('option:selected').text();
+ var backendClass = $(this).val();
+ $(this).parent().text(selected);
+ $(tr).find('.backend').data('class', backendClass);
+ var configurations = $(this).data('configurations');
+ var td = $(tr).find('td.configuration');
+ $.each(configurations, function(backend, parameters) {
+ if (backend == backendClass) {
+ $.each(parameters['configuration'], function(parameter, placeholder) {
+ if (placeholder.indexOf('*') != -1) {
+ td.append('<input type="password" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
+ } else if (placeholder.indexOf('!') != -1) {
+ td.append('<label><input type="checkbox" data-parameter="'+parameter+'" />'+placeholder.substring(1)+'</label>');
+ } else if (placeholder.indexOf('&') != -1) {
+ td.append('<input type="text" class="optional" data-parameter="'+parameter+'" placeholder="'+placeholder.substring(1)+'" />');
+ } else if (placeholder.indexOf('#') != -1) {
+ td.append('<input type="hidden" data-parameter="'+parameter+'" />');
+ } else {
+ td.append('<input type="text" data-parameter="'+parameter+'" placeholder="'+placeholder+'" />');
+ }
+ });
+ if (parameters['custom']) {
+ OC.addScript('files_external', parameters['custom']);
+ }
+ return false;
+ }
+ });
+ $('.chz-select').chosen();
+ $(tr).find('td').last().attr('class', 'remove');
+ $(tr).removeAttr('id');
+ $(this).remove();
+ });
+
+ $('#externalStorage td').live('change', function() {
+ OC.MountConfig.saveStorage($(this).parent());
});
$('td.remove>img').live('click', function() {
@@ -130,8 +140,6 @@ $(document).ready(function() {
$(tr).remove();
});
-
-
$('#allowUserMounting').bind('change', function() {
if (this.checked) {
OC.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes');
diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php
index 56a61e9ab89..870c13b5aed 100755
--- a/apps/files_external/lib/config.php
+++ b/apps/files_external/lib/config.php
@@ -30,16 +30,20 @@ class OC_Mount_Config {
/**
* Get details on each of the external storage backends, used for the mount config UI
+ * If a custom UI is needed, add the key 'custom' and a javascript file with that name will be loaded
* If the configuration parameter should be secret, add a '*' to the beginning of the value
* If the configuration parameter is a boolean, add a '!' to the beginning of the value
* If the configuration parameter is optional, add a '&' to the beginning of the value
+ * If the configuration parameter is hidden, add a '#' to the begining of the value
* @return array
*/
public static function getBackends() {
return array(
'OC_Filestorage_Local' => array('backend' => 'Local', 'configuration' => array('datadir' => 'Location')),
'OC_Filestorage_AmazonS3' => array('backend' => 'Amazon S3', 'configuration' => array('key' => 'Key', 'secret' => '*Secret', 'bucket' => 'Bucket')),
+ 'OC_Filestorage_Dropbox' => array('backend' => 'Dropbox', 'configuration' => array('app_key' => 'App key', 'app_secret' => 'App secret', 'token' => '#token', 'token_secret' => '#token_secret'), 'custom' => 'dropbox'),
'OC_Filestorage_FTP' => array('backend' => 'FTP', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure ftps://')),
+ 'OC_Filestorage_Google' => array('backend' => 'Google Drive', 'configuration' => array('token' => '#token', 'token_secret' => '#token secret'), 'custom' => 'google'),
'OC_Filestorage_SWIFT' => array('backend' => 'OpenStack Swift', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'token' => '*Token', 'root' => '&Root', 'secure' => '!Secure ftps://')),
'OC_Filestorage_SMB' => array('backend' => 'SMB', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root')),
'OC_Filestorage_DAV' => array('backend' => 'WebDAV', 'configuration' => array('host' => 'URL', 'user' => 'Username', 'password' => '*Password', 'root' => '&Root', 'secure' => '!Secure https://'))
diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php
index d2285a6d82c..c2a4af0ff8a 100644
--- a/apps/files_external/lib/google.php
+++ b/apps/files_external/lib/google.php
@@ -20,7 +20,7 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
-require_once 'common.inc.php';
+require_once 'Google/common.inc.php';
class OC_Filestorage_Google extends OC_Filestorage_Common {
diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php
index 9f65cfca965..6c37df8001e 100644
--- a/apps/files_external/templates/settings.php
+++ b/apps/files_external/templates/settings.php
@@ -40,11 +40,14 @@
<label><input type="checkbox" data-parameter="<?php echo $parameter; ?>" <?php if ($value == 'true') echo ' checked="checked"'; ?> /><?php echo substr($placeholder, 1); ?></label>
<?php elseif (strpos($placeholder, '&') !== false): ?>
<input type="text" class="optional" data-parameter="<?php echo $parameter; ?>" value="<?php echo $value; ?>" placeholder="<?php echo substr($placeholder, 1); ?>" />
+ <?php elseif (strpos($placeholder, '#') !== false): ?>
+ <input type="hidden" data-parameter="<?php echo $parameter; ?>" value="<?php echo $value; ?>" />
<?php else: ?>
<input type="text" data-parameter="<?php echo $parameter; ?>" value="<?php echo $value; ?>" placeholder="<?php echo $placeholder; ?>" />
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
+ <?php if (isset($_['backends'][$mount['class']]['custom'])) OCP\Util::addScript('files_external', $_['backends'][$mount['class']]['custom']); ?>
<?php endif; ?>
</td>
<!--<td class="options">
diff --git a/apps/files_pdfviewer/appinfo/app.php b/apps/files_pdfviewer/appinfo/app.php
index 06b15670674..f700d004dca 100644
--- a/apps/files_pdfviewer/appinfo/app.php
+++ b/apps/files_pdfviewer/appinfo/app.php
@@ -3,5 +3,6 @@
OCP\Util::addscript( 'files_pdfviewer', 'viewer');
OCP\Util::addStyle( 'files_pdfviewer', 'viewer');
OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/build/pdf');
-OCP\Util::addscript( 'files_pdfviewer', 'pdfview');
+OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/compatibility');
+OCP\Util::addscript( 'files_pdfviewer', 'pdfjs/viewer');
?>
diff --git a/apps/files_pdfviewer/appinfo/info.xml b/apps/files_pdfviewer/appinfo/info.xml
index e3813be1001..074962f57fb 100644
--- a/apps/files_pdfviewer/appinfo/info.xml
+++ b/apps/files_pdfviewer/appinfo/info.xml
@@ -4,7 +4,7 @@
<name>PDF Viewer</name>
<description>Inline PDF viewer (pdfjs-based)</description>
<licence>GPL</licence>
- <author>Joan Creus</author>
+ <author>Joan Creus, Thomas Müller</author>
<require>4</require>
<shipped>true</shipped>
<default_enable/>
diff --git a/apps/files_pdfviewer/css/viewer.css b/apps/files_pdfviewer/css/viewer.css
index b735dbfedfc..f9ce929d8b8 100644
--- a/apps/files_pdfviewer/css/viewer.css
+++ b/apps/files_pdfviewer/css/viewer.css
@@ -2,7 +2,6 @@
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
#viewer {
- background-color: #929292;
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
/*margin: 0px;*/
padding: 0px;
diff --git a/apps/files_pdfviewer/js/pdfjs/build/pdf.js b/apps/files_pdfviewer/js/pdfjs/build/pdf.js
index a19a9b75fea..b1fc9d9747e 100644
--- a/apps/files_pdfviewer/js/pdfjs/build/pdf.js
+++ b/apps/files_pdfviewer/js/pdfjs/build/pdf.js
@@ -7,10 +7,9 @@ var PDFJS = {};
// Use strict in our context only - users might not want it
'use strict';
- PDFJS.build = 'd823592';
+ PDFJS.build = '2aae4fd';
// Files are inserted below - see Makefile
- /* PDFJSSCRIPT_INCLUDE_ALL */
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -20,7 +19,7 @@ var globalScope = (typeof window === 'undefined') ? this : window;
var isWorker = (typeof window == 'undefined');
-var ERRORS = 0, WARNINGS = 1, TODOS = 5;
+var ERRORS = 0, WARNINGS = 1, INFOS = 5;
var verbosity = WARNINGS;
// The global PDFJS object exposes the API
@@ -44,7 +43,19 @@ function getPdf(arg, callback) {
params = { url: arg };
var xhr = new XMLHttpRequest();
+
xhr.open('GET', params.url);
+
+ var headers = params.headers;
+ if (headers) {
+ for (var property in headers) {
+ if (typeof headers[property] === 'undefined')
+ continue;
+
+ xhr.setRequestHeader(property, params.headers[property]);
+ }
+ }
+
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
var protocol = params.url.indexOf(':') < 0 ? window.location.protocol :
params.url.substring(0, params.url.indexOf(':') + 1);
@@ -76,8 +87,6 @@ var Page = (function PageClosure() {
function Page(xref, pageNumber, pageDict, ref) {
this.pageNumber = pageNumber;
this.pageDict = pageDict;
- this.stats = new StatTimer();
- this.stats.enabled = !!globalScope.PDFJS.enableStats;
this.xref = xref;
this.ref = ref;
@@ -113,18 +122,10 @@ var Page = (function PageClosure() {
return shadow(this, 'mediaBox', obj);
},
get view() {
+ var mediaBox = this.mediaBox;
var cropBox = this.inheritPageProp('CropBox');
- var view = {
- x: 0,
- y: 0,
- width: this.width,
- height: this.height
- };
if (!isArray(cropBox) || cropBox.length !== 4)
- return shadow(this, 'view', view);
-
- var mediaBox = this.mediaBox;
- var offsetX = mediaBox[0], offsetY = mediaBox[1];
+ return shadow(this, 'view', mediaBox);
// From the spec, 6th ed., p.963:
// "The crop, bleed, trim, and art boxes should not ordinarily
@@ -132,42 +133,13 @@ var Page = (function PageClosure() {
// effectively reduced to their intersection with the media box."
cropBox = Util.intersect(cropBox, mediaBox);
if (!cropBox)
- return shadow(this, 'view', view);
-
- var tl = this.rotatePoint(cropBox[0] - offsetX, cropBox[1] - offsetY);
- var br = this.rotatePoint(cropBox[2] - offsetX, cropBox[3] - offsetY);
- view.x = Math.min(tl.x, br.x);
- view.y = Math.min(tl.y, br.y);
- view.width = Math.abs(tl.x - br.x);
- view.height = Math.abs(tl.y - br.y);
+ return shadow(this, 'view', mediaBox);
- return shadow(this, 'view', view);
+ return shadow(this, 'view', cropBox);
},
get annotations() {
return shadow(this, 'annotations', this.inheritPageProp('Annots'));
},
- get width() {
- var mediaBox = this.mediaBox;
- var rotate = this.rotate;
- var width;
- if (rotate == 0 || rotate == 180) {
- width = (mediaBox[2] - mediaBox[0]);
- } else {
- width = (mediaBox[3] - mediaBox[1]);
- }
- return shadow(this, 'width', width);
- },
- get height() {
- var mediaBox = this.mediaBox;
- var rotate = this.rotate;
- var height;
- if (rotate == 0 || rotate == 180) {
- height = (mediaBox[3] - mediaBox[1]);
- } else {
- height = (mediaBox[2] - mediaBox[0]);
- }
- return shadow(this, 'height', height);
- },
get rotate() {
var rotate = this.inheritPageProp('Rotate') || 0;
// Normalize rotation so it's a multiple of 90 and between 0 and 270
@@ -183,43 +155,20 @@ var Page = (function PageClosure() {
return shadow(this, 'rotate', rotate);
},
- startRenderingFromOperatorList:
- function Page_startRenderingFromOperatorList(operatorList, fonts) {
- var self = this;
- this.operatorList = operatorList;
-
- var displayContinuation = function pageDisplayContinuation() {
- // Always defer call to display() to work around bug in
- // Firefox error reporting from XHR callbacks.
- setTimeout(function pageSetTimeout() {
- self.displayReadyPromise.resolve();
- });
- };
-
- this.ensureFonts(fonts,
- function pageStartRenderingFromOperatorListEnsureFonts() {
- displayContinuation();
- }
- );
- },
-
getOperatorList: function Page_getOperatorList(handler, dependency) {
- if (this.operatorList) {
- // content was compiled
- return this.operatorList;
- }
-
- this.stats.time('Build IR Queue');
-
var xref = this.xref;
var content = this.content;
var resources = this.resources;
if (isArray(content)) {
// fetching items
+ var streams = [];
var i, n = content.length;
+ var streams = [];
for (i = 0; i < n; ++i)
- content[i] = xref.fetchIfRef(content[i]);
- content = new StreamsSequenceStream(content);
+ streams.push(xref.fetchIfRef(content[i]));
+ content = new StreamsSequenceStream(streams);
+ } else if (isStream(content)) {
+ content.reset();
} else if (!content) {
// replacing non-existent page content with empty one
content = new Stream(new Uint8Array(0));
@@ -228,9 +177,31 @@ var Page = (function PageClosure() {
var pe = this.pe = new PartialEvaluator(
xref, handler, 'p' + this.pageNumber + '_');
- this.operatorList = pe.getOperatorList(content, resources, dependency);
- this.stats.timeEnd('Build IR Queue');
- return this.operatorList;
+ return pe.getOperatorList(content, resources, dependency);
+ },
+ extractTextContent: function Page_extractTextContent() {
+ var handler = {
+ on: function nullHandlerOn() {},
+ send: function nullHandlerSend() {}
+ };
+
+ var xref = this.xref;
+ var content = xref.fetchIfRef(this.content);
+ var resources = xref.fetchIfRef(this.resources);
+ if (isArray(content)) {
+ // fetching items
+ var i, n = content.length;
+ var streams = [];
+ for (i = 0; i < n; ++i)
+ streams.push(xref.fetchIfRef(content[i]));
+ content = new StreamsSequenceStream(streams);
+ } else if (isStream(content)) {
+ content.reset();
+ }
+
+ var pe = new PartialEvaluator(
+ xref, handler, 'p' + this.pageNumber + '_');
+ return pe.getTextContent(content, resources);
},
ensureFonts: function Page_ensureFonts(fonts, callback) {
@@ -250,60 +221,6 @@ var Page = (function PageClosure() {
}.bind(this)
);
},
-
- display: function Page_display(gfx, callback) {
- var stats = this.stats;
- stats.time('Rendering');
- var xref = this.xref;
- var resources = this.resources;
- var mediaBox = this.mediaBox;
- assertWellFormed(isDict(resources), 'invalid page resources');
-
- gfx.xref = xref;
- gfx.res = resources;
- gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
- width: this.width,
- height: this.height,
- rotate: this.rotate });
-
- var startIdx = 0;
- var length = this.operatorList.fnArray.length;
- var operatorList = this.operatorList;
- var stepper = null;
- if (PDFJS.pdfBug && StepperManager.enabled) {
- stepper = StepperManager.create(this.pageNumber);
- stepper.init(operatorList);
- stepper.nextBreakPoint = stepper.getNextBreakPoint();
- }
-
- var self = this;
- function next() {
- startIdx =
- gfx.executeOperatorList(operatorList, startIdx, next, stepper);
- if (startIdx == length) {
- gfx.endDrawing();
- stats.timeEnd('Rendering');
- stats.timeEnd('Overall');
- if (callback) callback();
- }
- }
- next();
- },
- rotatePoint: function Page_rotatePoint(x, y, reverse) {
- var rotate = reverse ? (360 - this.rotate) : this.rotate;
- switch (rotate) {
- case 180:
- return {x: this.width - x, y: y};
- case 90:
- return {x: this.width - y, y: this.height - x};
- case 270:
- return {x: y, y: x};
- case 360:
- case 0:
- default:
- return {x: x, y: this.height - y};
- }
- },
getLinks: function Page_getLinks() {
var links = [];
var annotations = pageGetAnnotations();
@@ -337,6 +254,7 @@ var Page = (function PageClosure() {
case 'http':
case 'https':
case 'ftp':
+ case 'mailto':
return true;
default:
return false;
@@ -355,15 +273,10 @@ var Page = (function PageClosure() {
if (!isName(subtype))
continue;
var rect = annotation.get('Rect');
- var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
- var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
var item = {};
item.type = subtype.name;
- item.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
- item.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
- item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
- item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
+ item.rect = rect;
switch (subtype.name) {
case 'Link':
var a = annotation.get('A');
@@ -437,7 +350,8 @@ var Page = (function PageClosure() {
var title = annotation.get('T');
item.content = stringToPDFString(content || '');
item.title = stringToPDFString(title || '');
- item.name = annotation.get('Name').name;
+ item.name = !annotation.has('Name') ? 'Note' :
+ annotation.get('Name').name;
break;
default:
TODO('unimplemented annotation type: ' + subtype.name);
@@ -446,37 +360,6 @@ var Page = (function PageClosure() {
items.push(item);
}
return items;
- },
- startRendering: function Page_startRendering(ctx, callback, textLayer) {
- var stats = this.stats;
- stats.time('Overall');
- // If there is no displayReadyPromise yet, then the operatorList was never
- // requested before. Make the request and create the promise.
- if (!this.displayReadyPromise) {
- this.pdf.startRendering(this);
- this.displayReadyPromise = new Promise();
- }
-
- // Once the operatorList and fonts are loaded, do the actual rendering.
- this.displayReadyPromise.then(
- function pageDisplayReadyPromise() {
- var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
- try {
- this.display(gfx, callback);
- } catch (e) {
- if (callback)
- callback(e);
- else
- error(e);
- }
- }.bind(this),
- function pageDisplayReadPromiseError(reason) {
- if (callback)
- callback(reason);
- else
- error(reason);
- }
- );
}
};
@@ -484,26 +367,26 @@ var Page = (function PageClosure() {
})();
/**
- * The `PDFDocModel` holds all the data of the PDF file. Compared to the
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
* `PDFDoc`, this one doesn't have any job management code.
- * Right now there exists one PDFDocModel on the main thread + one object
+ * Right now there exists one PDFDocument on the main thread + one object
* for each worker. If there is no worker support enabled, there are two
- * `PDFDocModel` objects on the main thread created.
+ * `PDFDocument` objects on the main thread created.
*/
-var PDFDocModel = (function PDFDocModelClosure() {
- function PDFDocModel(arg, callback) {
+var PDFDocument = (function PDFDocumentClosure() {
+ function PDFDocument(arg, password) {
if (isStream(arg))
- init.call(this, arg);
+ init.call(this, arg, password);
else if (isArrayBuffer(arg))
- init.call(this, new Stream(arg));
+ init.call(this, new Stream(arg), password);
else
- error('PDFDocModel: Unknown argument type');
+ error('PDFDocument: Unknown argument type');
}
- function init(stream) {
+ function init(stream, password) {
assertWellFormed(stream.length > 0, 'stream must have data');
this.stream = stream;
- this.setup();
+ this.setup(password);
this.acroForm = this.catalog.catDict.get('AcroForm');
}
@@ -523,7 +406,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
return true; /* found */
}
- PDFDocModel.prototype = {
+ PDFDocument.prototype = {
get linearization() {
var length = this.stream.length;
var linearization = false;
@@ -584,7 +467,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
},
// Find the header, remove leading garbage and setup the stream
// starting from the header.
- checkHeader: function PDFDocModel_checkHeader() {
+ checkHeader: function PDFDocument_checkHeader() {
var stream = this.stream;
stream.reset();
if (find(stream, '%PDF-', 1024)) {
@@ -594,11 +477,12 @@ var PDFDocModel = (function PDFDocModelClosure() {
}
// May not be a PDF file, continue anyway.
},
- setup: function PDFDocModel_setup(ownerPassword, userPassword) {
+ setup: function PDFDocument_setup(password) {
this.checkHeader();
var xref = new XRef(this.stream,
this.startXRef,
- this.mainXRefEntriesOffset);
+ this.mainXRefEntriesOffset,
+ password);
this.xref = xref;
this.catalog = new Catalog(xref);
},
@@ -608,7 +492,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
// shadow the prototype getter
return shadow(this, 'numPages', num);
},
- getDocumentInfo: function PDFDocModel_getDocumentInfo() {
+ getDocumentInfo: function PDFDocument_getDocumentInfo() {
var info;
if (this.xref.trailer.has('Info')) {
var infoDict = this.xref.trailer.get('Info');
@@ -622,7 +506,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
return shadow(this, 'getDocumentInfo', info);
},
- getFingerprint: function PDFDocModel_getFingerprint() {
+ getFingerprint: function PDFDocument_getFingerprint() {
var xref = this.xref, fileID;
if (xref.trailer.has('ID')) {
fileID = '';
@@ -643,259 +527,22 @@ var PDFDocModel = (function PDFDocModelClosure() {
return shadow(this, 'getFingerprint', fileID);
},
- getPage: function PDFDocModel_getPage(n) {
+ getPage: function PDFDocument_getPage(n) {
return this.catalog.getPage(n);
}
};
- return PDFDocModel;
+ return PDFDocument;
})();
-var PDFDoc = (function PDFDocClosure() {
- function PDFDoc(arg, callback) {
- var stream = null;
- var data = null;
-
- if (isStream(arg)) {
- stream = arg;
- data = arg.bytes;
- } else if (isArrayBuffer(arg)) {
- stream = new Stream(arg);
- data = arg;
- } else {
- error('PDFDoc: Unknown argument type');
- }
-
- this.data = data;
- this.stream = stream;
- this.pdfModel = new PDFDocModel(stream);
- this.fingerprint = this.pdfModel.getFingerprint();
- this.info = this.pdfModel.getDocumentInfo();
- this.catalog = this.pdfModel.catalog;
- this.objs = new PDFObjects();
-
- this.pageCache = [];
- this.fontsLoading = {};
- this.workerReadyPromise = new Promise('workerReady');
-
- // If worker support isn't disabled explicit and the browser has worker
- // support, create a new web worker and test if it/the browser fullfills
- // all requirements to run parts of pdf.js in a web worker.
- // Right now, the requirement is, that an Uint8Array is still an Uint8Array
- // as it arrives on the worker. Chrome added this with version 15.
- if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
- var workerSrc = PDFJS.workerSrc;
- if (typeof workerSrc === 'undefined') {
- error('No PDFJS.workerSrc specified');
- }
-
- try {
- var worker;
- if (PDFJS.isFirefoxExtension) {
- // The firefox extension can't load the worker from the resource://
- // url so we have to inline the script and then use the blob loader.
- var bb = new MozBlobBuilder();
- bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
- var blobUrl = window.URL.createObjectURL(bb.getBlob());
- worker = new Worker(blobUrl);
- } else {
- // Some versions of FF can't create a worker on localhost, see:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
- worker = new Worker(workerSrc);
- }
-
- var messageHandler = new MessageHandler('main', worker);
-
- messageHandler.on('test', function pdfDocTest(supportTypedArray) {
- if (supportTypedArray) {
- this.worker = worker;
- this.setupMessageHandler(messageHandler);
- } else {
- globalScope.PDFJS.disableWorker = true;
- this.setupFakeWorker();
- }
- }.bind(this));
-
- var testObj = new Uint8Array(1);
- // Some versions of Opera throw a DATA_CLONE_ERR on
- // serializing the typed array.
- messageHandler.send('test', testObj);
- return;
- } catch (e) {
- warn('The worker has been disabled.');
- }
- }
- // Either workers are disabled, not supported or have thrown an exception.
- // Thus, we fallback to a faked worker.
- globalScope.PDFJS.disableWorker = true;
- this.setupFakeWorker();
- }
-
- PDFDoc.prototype = {
- setupFakeWorker: function PDFDoc_setupFakeWorker() {
- // If we don't use a worker, just post/sendMessage to the main thread.
- var fakeWorker = {
- postMessage: function PDFDoc_postMessage(obj) {
- fakeWorker.onmessage({data: obj});
- },
- terminate: function PDFDoc_terminate() {}
- };
-
- var messageHandler = new MessageHandler('main', fakeWorker);
- this.setupMessageHandler(messageHandler);
-
- // If the main thread is our worker, setup the handling for the messages
- // the main thread sends to it self.
- WorkerMessageHandler.setup(messageHandler);
- },
-
-
- setupMessageHandler: function PDFDoc_setupMessageHandler(messageHandler) {
- this.messageHandler = messageHandler;
-
- messageHandler.on('page', function pdfDocPage(data) {
- var pageNum = data.pageNum;
- var page = this.pageCache[pageNum];
- var depFonts = data.depFonts;
-
- page.stats.timeEnd('Page Request');
- page.startRenderingFromOperatorList(data.operatorList, depFonts);
- }, this);
-
- messageHandler.on('obj', function pdfDocObj(data) {
- var id = data[0];
- var type = data[1];
-
- switch (type) {
- case 'JpegStream':
- var imageData = data[2];
- loadJpegStream(id, imageData, this.objs);
- break;
- case 'Image':
- var imageData = data[2];
- this.objs.resolve(id, imageData);
- break;
- case 'Font':
- var name = data[2];
- var file = data[3];
- var properties = data[4];
-
- if (file) {
- // Rewrap the ArrayBuffer in a stream.
- var fontFileDict = new Dict();
- file = new Stream(file, 0, file.length, fontFileDict);
- }
-
- // At this point, only the font object is created but the font is
- // not yet attached to the DOM. This is done in `FontLoader.bind`.
- var font = new Font(name, file, properties);
- this.objs.resolve(id, font);
- break;
- default:
- error('Got unkown object type ' + type);
- }
- }, this);
-
- messageHandler.on('page_error', function pdfDocError(data) {
- var page = this.pageCache[data.pageNum];
- if (page.displayReadyPromise)
- page.displayReadyPromise.reject(data.error);
- else
- error(data.error);
- }, this);
-
- messageHandler.on('jpeg_decode', function(data, promise) {
- var imageData = data[0];
- var components = data[1];
- if (components != 3 && components != 1)
- error('Only 3 component or 1 component can be returned');
-
- var img = new Image();
- img.onload = (function messageHandler_onloadClosure() {
- var width = img.width;
- var height = img.height;
- var size = width * height;
- var rgbaLength = size * 4;
- var buf = new Uint8Array(size * components);
- var tmpCanvas = createScratchCanvas(width, height);
- var tmpCtx = tmpCanvas.getContext('2d');
- tmpCtx.drawImage(img, 0, 0);
- var data = tmpCtx.getImageData(0, 0, width, height).data;
-
- if (components == 3) {
- for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
- buf[j] = data[i];
- buf[j + 1] = data[i + 1];
- buf[j + 2] = data[i + 2];
- }
- } else if (components == 1) {
- for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
- buf[j] = data[i];
- }
- }
- promise.resolve({ data: buf, width: width, height: height});
- }).bind(this);
- var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
- img.src = src;
- });
-
- setTimeout(function pdfDocFontReadySetTimeout() {
- messageHandler.send('doc', this.data);
- this.workerReadyPromise.resolve(true);
- }.bind(this));
- },
-
- get numPages() {
- return this.pdfModel.numPages;
- },
-
- startRendering: function PDFDoc_startRendering(page) {
- // The worker might not be ready to receive the page request yet.
- this.workerReadyPromise.then(function pdfDocStartRenderingThen() {
- page.stats.time('Page Request');
- this.messageHandler.send('page_request', page.pageNumber + 1);
- }.bind(this));
- },
-
- getPage: function PDFDoc_getPage(n) {
- if (this.pageCache[n])
- return this.pageCache[n];
-
- var page = this.pdfModel.getPage(n);
- // Add a reference to the objects such that Page can forward the reference
- // to the CanvasGraphics and so on.
- page.objs = this.objs;
- page.pdf = this;
- return (this.pageCache[n] = page);
- },
-
- destroy: function PDFDoc_destroy() {
- if (this.worker)
- this.worker.terminate();
-
- if (this.fontWorker)
- this.fontWorker.terminate();
-
- for (var n in this.pageCache)
- delete this.pageCache[n];
-
- delete this.data;
- delete this.stream;
- delete this.pdf;
- delete this.catalog;
- }
- };
-
- return PDFDoc;
-})();
-
-globalScope.PDFJS.PDFDoc = PDFDoc;
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
+// Use only for debugging purposes. This should not be used in any code that is
+// in mozilla master.
function log(msg) {
if (console && console.log)
console.log(msg);
@@ -903,32 +550,44 @@ function log(msg) {
print(msg);
}
-function warn(msg) {
- if (verbosity >= WARNINGS)
- log('Warning: ' + msg);
+// A notice for devs that will not trigger the fallback UI. These are good
+// for things that are helpful to devs, such as warning that Workers were
+// disabled, which is important to devs but not end users.
+function info(msg) {
+ if (verbosity >= INFOS) {
+ log('Info: ' + msg);
+ PDFJS.LogManager.notify('info', msg);
+ }
}
-function backtrace() {
- try {
- throw new Error();
- } catch (e) {
- return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+// Non-fatal warnings that should trigger the fallback UI.
+function warn(msg) {
+ if (verbosity >= WARNINGS) {
+ log('Warning: ' + msg);
+ PDFJS.LogManager.notify('warn', msg);
}
}
+// Fatal errors that should trigger the fallback UI and halt execution by
+// throwing an exception.
function error(msg) {
log('Error: ' + msg);
log(backtrace());
+ PDFJS.LogManager.notify('error', msg);
throw new Error(msg);
}
+// Missing features that should trigger the fallback UI.
function TODO(what) {
- if (verbosity >= TODOS)
- log('TODO: ' + what);
+ warn('TODO: ' + what);
}
-function malformed(msg) {
- error('Malformed PDF: ' + msg);
+function backtrace() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
}
function assert(cond, msg) {
@@ -940,9 +599,25 @@ function assert(cond, msg) {
// behavior is undefined.
function assertWellFormed(cond, msg) {
if (!cond)
- malformed(msg);
+ error(msg);
}
+var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
+ var loggers = [];
+ return {
+ addLogger: function logManager_addLogger(logger) {
+ loggers.push(logger);
+ },
+ notify: function(type, message) {
+ for (var i = 0, ii = loggers.length; i < ii; i++) {
+ var logger = loggers[i];
+ if (logger[type])
+ logger[type](message);
+ }
+ }
+ };
+})();
+
function shadow(obj, prop, value) {
Object.defineProperty(obj, prop, { value: value,
enumerable: true,
@@ -951,6 +626,19 @@ function shadow(obj, prop, value) {
return value;
}
+var PasswordException = (function PasswordExceptionClosure() {
+ function PasswordException(msg, code) {
+ this.name = 'PasswordException';
+ this.message = msg;
+ this.code = code;
+ }
+
+ PasswordException.prototype = new Error();
+ PasswordException.constructor = PasswordException;
+
+ return PasswordException;
+})();
+
function bytesToString(bytes) {
var str = '';
var length = bytes.length;
@@ -969,7 +657,7 @@ function stringToBytes(str) {
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
-var Util = (function UtilClosure() {
+var Util = PDFJS.Util = (function UtilClosure() {
function Util() {}
Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
@@ -990,6 +678,19 @@ var Util = (function UtilClosure() {
return [xt, yt];
};
+ Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [xt, yt];
+ };
+
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+ };
+
// Apply a generic 3d matrix M on a 3-vector v:
// | a b c | | X |
// | d e f | x | Y |
@@ -1058,7 +759,7 @@ var Util = (function UtilClosure() {
}
return result;
- }
+ };
Util.sign = function Util_sign(num) {
return num < 0 ? -1 : 1;
@@ -1067,6 +768,80 @@ var Util = (function UtilClosure() {
return Util;
})();
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+ function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ switch (rotate) {
+ case -180:
+ case 180:
+ rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+ break;
+ case -270:
+ case 90:
+ rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+ break;
+ case -90:
+ case 270:
+ rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+ break;
+ case 360:
+ case 0:
+ default:
+ rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+ break;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA == 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = {
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([x, y], this.transform);
+ },
+ convertToViewportRectangle:
+ function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+ var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+ return [tl[0], tl[1], br[0], br[1]];
+ },
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([x, y], this.transform);
+ }
+ };
+ return PageViewport;
+})();
+
var PDFStringTranslateTable = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
@@ -1095,6 +870,10 @@ function stringToPDFString(str) {
return str2;
}
+function stringToUTF8String(str) {
+ return decodeURIComponent(escape(str));
+}
+
function isBool(v) {
return typeof v == 'boolean';
}
@@ -1168,7 +947,7 @@ function isPDFFunction(v) {
* can be set. If any of these happens twice or the data is required before
* it was set, an exception is throw.
*/
-var Promise = (function PromiseClosure() {
+var Promise = PDFJS.Promise = (function PromiseClosure() {
var EMPTY_PROMISE = {};
/**
@@ -1190,6 +969,7 @@ var Promise = (function PromiseClosure() {
}
this.callbacks = [];
this.errbacks = [];
+ this.progressbacks = [];
};
/**
* Builds a promise that is resolved when all the passed in promises are
@@ -1205,7 +985,7 @@ var Promise = (function PromiseClosure() {
deferred.resolve(results);
return deferred;
}
- for (var i = 0; i < unresolved; ++i) {
+ for (var i = 0, ii = promises.length; i < ii; ++i) {
var promise = promises[i];
promise.then((function(i) {
return function(value) {
@@ -1261,7 +1041,7 @@ var Promise = (function PromiseClosure() {
}
this.isResolved = true;
- this.data = data || null;
+ this.data = (typeof data !== 'undefined') ? data : null;
var callbacks = this.callbacks;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
@@ -1269,7 +1049,14 @@ var Promise = (function PromiseClosure() {
}
},
- reject: function Promise_reject(reason) {
+ progress: function Promise_progress(data) {
+ var callbacks = this.progressbacks;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ reject: function Promise_reject(reason, exception) {
if (this.isRejected) {
error('A Promise can be rejected only once ' + this.name);
}
@@ -1282,11 +1069,11 @@ var Promise = (function PromiseClosure() {
var errbacks = this.errbacks;
for (var i = 0, ii = errbacks.length; i < ii; i++) {
- errbacks[i].call(null, reason);
+ errbacks[i].call(null, reason, exception);
}
},
- then: function Promise_then(callback, errback) {
+ then: function Promise_then(callback, errback, progressback) {
if (!callback) {
error('Requiring callback' + this.name);
}
@@ -1303,6 +1090,9 @@ var Promise = (function PromiseClosure() {
if (errback)
this.errbacks.push(errback);
}
+
+ if (progressback)
+ this.progressbacks.push(progressback);
}
};
@@ -1361,6 +1151,659 @@ var StatTimer = (function StatTimerClosure() {
};
return StatTimer;
})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedAray|object} source Can be an url to where a PDF is
+ * located, a typed array (Uint8Array) already populated with data or
+ * and parameter object with the following possible fields:
+ * - url - The URL of the PDF.
+ * - data - A typed array with PDF data.
+ * - httpHeaders - Basic authentication headers.
+ * - password - For decrypting password-protected PDFs.
+ *
+ * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
+ */
+PDFJS.getDocument = function getDocument(source) {
+ var url, data, headers, password, parameters = {};
+ if (typeof source === 'string') {
+ url = source;
+ } else if (isArrayBuffer(source)) {
+ data = source;
+ } else if (typeof source === 'object') {
+ url = source.url;
+ data = source.data;
+ headers = source.httpHeaders;
+ password = source.password;
+ parameters.password = password || null;
+
+ if (!url && !data)
+ error('Invalid parameter array, need either .data or .url');
+ } else {
+ error('Invalid parameter in getDocument, need either Uint8Array, ' +
+ 'string or a parameter object');
+ }
+
+ var promise = new PDFJS.Promise();
+ var transport = new WorkerTransport(promise);
+ if (data) {
+ // assuming the data is array, instantiating directly from it
+ transport.sendData(data, parameters);
+ } else if (url) {
+ // fetch url
+ PDFJS.getPdf(
+ {
+ url: url,
+ progress: function getPDFProgress(evt) {
+ if (evt.lengthComputable)
+ promise.progress({
+ loaded: evt.loaded,
+ total: evt.total
+ });
+ },
+ error: function getPDFError(e) {
+ promise.reject('Unexpected server response of ' +
+ e.target.status + '.');
+ },
+ headers: headers
+ },
+ function getPDFLoad(data) {
+ transport.sendData(data, parameters);
+ });
+ }
+
+ return promise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
+ function PDFDocumentProxy(pdfInfo, transport) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ }
+ PDFDocumentProxy.prototype = {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @param {number} The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+ * object.
+ */
+ getPage: function PDFDocumentProxy_getPage(number) {
+ return this.transport.getPage(number);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ */
+ getDestinations: function PDFDocumentProxy_getDestinations() {
+ var promise = new PDFJS.Promise();
+ var destinations = this.pdfInfo.destinations;
+ promise.resolve(destinations);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb array,
+ * dest: dest obj,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function PDFDocumentProxy_getOutline() {
+ var promise = new PDFJS.Promise();
+ var outline = this.pdfInfo.outline;
+ promise.resolve(outline);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {object} that has
+ * info and metadata properties. Info is an {object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function PDFDocumentProxy_getMetadata() {
+ var promise = new PDFJS.Promise();
+ var info = this.pdfInfo.info;
+ var metadata = this.pdfInfo.metadata;
+ promise.resolve({
+ info: info,
+ metadata: metadata ? new PDFJS.Metadata(metadata) : null
+ });
+ return promise;
+ },
+ isEncrypted: function PDFDocumentProxy_isEncrypted() {
+ var promise = new PDFJS.Promise();
+ promise.resolve(this.pdfInfo.encrypted);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a TypedArray that has
+ * the raw data from the PDF.
+ */
+ getData: function PDFDocumentProxy_getData() {
+ var promise = new PDFJS.Promise();
+ this.transport.getData(promise);
+ return promise;
+ },
+ destroy: function PDFDocumentProxy_destroy() {
+ this.transport.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+})();
+
+var PDFPageProxy = (function PDFPageProxyClosure() {
+ function PDFPageProxy(pageInfo, transport) {
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
+ this.objs = transport.objs;
+ this.renderInProgress = false;
+ }
+ PDFPageProxy.prototype = {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageInfo.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PageViewport} Contains 'width' and 'height' properties along
+ * with transforms required for rendering.
+ */
+ getViewport: function PDFPageProxy_getViewport(scale, rotate) {
+ if (arguments.length < 2)
+ rotate = this.rotate;
+ return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} of the
+ * annotation objects.
+ */
+ getAnnotations: function PDFPageProxy_getAnnotations() {
+ if (this.annotationsPromise)
+ return this.annotationsPromise;
+
+ var promise = new PDFJS.Promise();
+ this.annotationsPromise = promise;
+ this.transport.getAnnotations(this.pageInfo.pageIndex);
+ return promise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {object} params A parameter object that supports:
+ * {
+ * canvasContext(required): A 2D context of a DOM Canvas object.,
+ * textLayer(optional): An object that has beginLayout, endLayout, and
+ * appendText functions.
+ * }.
+ * @return {Promise} A promise that is resolved when the page finishes
+ * rendering.
+ */
+ render: function PDFPageProxy_render(params) {
+ this.renderInProgress = true;
+
+ var promise = new Promise();
+ var stats = this.stats;
+ stats.time('Overall');
+ // If there is no displayReadyPromise yet, then the operatorList was never
+ // requested before. Make the request and create the promise.
+ if (!this.displayReadyPromise) {
+ this.displayReadyPromise = new Promise();
+ this.destroyed = false;
+
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1
+ });
+ }
+
+ var self = this;
+ function complete(error) {
+ self.renderInProgress = false;
+ if (self.destroyed) {
+ delete self.operatorList;
+ delete self.displayReadyPromise;
+ }
+
+ if (error)
+ promise.reject(error);
+ else
+ promise.resolve();
+ };
+
+ // Once the operatorList and fonts are loaded, do the actual rendering.
+ this.displayReadyPromise.then(
+ function pageDisplayReadyPromise() {
+ if (self.destroyed) {
+ complete();
+ return;
+ }
+
+ var gfx = new CanvasGraphics(params.canvasContext,
+ this.objs, params.textLayer);
+ try {
+ this.display(gfx, params.viewport, complete);
+ } catch (e) {
+ complete(e);
+ }
+ }.bind(this),
+ function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ }
+ );
+
+ return promise;
+ },
+ /**
+ * For internal use only.
+ */
+ startRenderingFromOperatorList:
+ function PDFPageProxy_startRenderingFromOperatorList(operatorList,
+ fonts) {
+ var self = this;
+ this.operatorList = operatorList;
+
+ var displayContinuation = function pageDisplayContinuation() {
+ // Always defer call to display() to work around bug in
+ // Firefox error reporting from XHR callbacks.
+ setTimeout(function pageSetTimeout() {
+ self.displayReadyPromise.resolve();
+ });
+ };
+
+ this.ensureFonts(fonts,
+ function pageStartRenderingFromOperatorListEnsureFonts() {
+ displayContinuation();
+ }
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
+ this.stats.time('Font Loading');
+ // Convert the font names to the corresponding font obj.
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ fonts[i] = this.objs.objs[fonts[i]].data;
+ }
+
+ // Load all the fonts
+ FontLoader.bind(
+ fonts,
+ function pageEnsureFontsFontObjs(fontObjs) {
+ this.stats.timeEnd('Font Loading');
+
+ callback.call(this);
+ }.bind(this)
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ display: function PDFPageProxy_display(gfx, viewport, callback) {
+ var stats = this.stats;
+ stats.time('Rendering');
+
+ gfx.beginDrawing(viewport);
+
+ var startIdx = 0;
+ var length = this.operatorList.fnArray.length;
+ var operatorList = this.operatorList;
+ var stepper = null;
+ if (PDFJS.pdfBug && StepperManager.enabled) {
+ stepper = StepperManager.create(this.pageNumber - 1);
+ stepper.init(operatorList);
+ stepper.nextBreakPoint = stepper.getNextBreakPoint();
+ }
+
+ var self = this;
+ function next() {
+ startIdx =
+ gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+ if (startIdx == length) {
+ gfx.endDrawing();
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ if (callback) callback();
+ }
+ }
+ next();
+ },
+ /**
+ * @return {Promise} That is resolved with the a {string} that is the text
+ * content from the page.
+ */
+ getTextContent: function PDFPageProxy_getTextContent() {
+ var promise = new PDFJS.Promise();
+ this.transport.messageHandler.send('GetTextContent', {
+ pageIndex: this.pageNumber - 1
+ },
+ function textContentCallback(textContent) {
+ promise.resolve(textContent);
+ }
+ );
+ return promise;
+ },
+ /**
+ * Stub for future feature.
+ */
+ getOperationList: function PDFPageProxy_getOperationList() {
+ var promise = new PDFJS.Promise();
+ var operationList = { // not implemented
+ dependencyFontsID: null,
+ operatorList: null
+ };
+ promise.resolve(operationList);
+ return promise;
+ },
+ /**
+ * Destroys resources allocated by the page.
+ */
+ destroy: function PDFPageProxy_destroy() {
+ this.destroyed = true;
+
+ if (!this.renderInProgress) {
+ delete this.operatorList;
+ delete this.displayReadyPromise;
+ }
+ }
+ };
+ return PDFPageProxy;
+})();
+/**
+ * For internal use only.
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+ function WorkerTransport(promise) {
+ this.workerReadyPromise = promise;
+ this.objs = new PDFObjects();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.fontsLoading = {};
+
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+ // as it arrives on the worker. Chrome added this with version 15.
+ if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+ var workerSrc = PDFJS.workerSrc;
+ if (typeof workerSrc === 'undefined') {
+ error('No PDFJS.workerSrc specified');
+ }
+
+ try {
+ var worker;
+ if (PDFJS.isFirefoxExtension) {
+ // The firefox extension can't load the worker from the resource://
+ // url so we have to inline the script and then use the blob loader.
+ var bb = new MozBlobBuilder();
+ bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
+ var blobUrl = window.URL.createObjectURL(bb.getBlob());
+ worker = new Worker(blobUrl);
+ } else {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ worker = new Worker(workerSrc);
+ }
+
+ var messageHandler = new MessageHandler('main', worker);
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('test', function transportTest(supportTypedArray) {
+ if (supportTypedArray) {
+ this.worker = worker;
+ this.setupMessageHandler(messageHandler);
+ } else {
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ }.bind(this));
+
+ var testObj = new Uint8Array(1);
+ // Some versions of Opera throw a DATA_CLONE_ERR on
+ // serializing the typed array.
+ messageHandler.send('test', testObj);
+ return;
+ } catch (e) {
+ info('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ if (this.worker)
+ this.worker.terminate();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ },
+ setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var fakeWorker = {
+ postMessage: function WorkerTransport_postMessage(obj) {
+ fakeWorker.onmessage({data: obj});
+ },
+ terminate: function WorkerTransport_terminate() {}
+ };
+
+ var messageHandler = new MessageHandler('main', fakeWorker);
+ this.setupMessageHandler(messageHandler);
+
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ WorkerMessageHandler.setup(messageHandler);
+ },
+
+ setupMessageHandler:
+ function WorkerTransport_setupMessageHandler(messageHandler) {
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ this.pdfDocument = pdfDocument;
+ this.workerReadyPromise.resolve(pdfDocument);
+ }, this);
+
+ messageHandler.on('NeedPassword', function transportPassword(data) {
+ this.workerReadyPromise.reject(data.exception.message, data.exception);
+ }, this);
+
+ messageHandler.on('IncorrectPassword', function transportBadPass(data) {
+ this.workerReadyPromise.reject(data.exception.message, data.exception);
+ }, this);
+
+ messageHandler.on('GetPage', function transportPage(data) {
+ var pageInfo = data.pageInfo;
+ var page = new PDFPageProxy(pageInfo, this);
+ this.pageCache[pageInfo.pageIndex] = page;
+ var promise = this.pagePromises[pageInfo.pageIndex];
+ promise.resolve(page);
+ }, this);
+
+ messageHandler.on('GetAnnotations', function transportAnnotations(data) {
+ var annotations = data.annotations;
+ var promise = this.pageCache[data.pageIndex].annotationsPromise;
+ promise.resolve(annotations);
+ }, this);
+
+ messageHandler.on('RenderPage', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+ var depFonts = data.depFonts;
+
+ page.stats.timeEnd('Page Request');
+ page.startRenderingFromOperatorList(data.operatorList, depFonts);
+ }, this);
+
+ messageHandler.on('obj', function transportObj(data) {
+ var id = data[0];
+ var type = data[1];
+ if (this.objs.hasData(id))
+ return;
+
+ switch (type) {
+ case 'JpegStream':
+ var imageData = data[2];
+ loadJpegStream(id, imageData, this.objs);
+ break;
+ case 'Image':
+ var imageData = data[2];
+ this.objs.resolve(id, imageData);
+ break;
+ case 'Font':
+ var name = data[2];
+ var file = data[3];
+ var properties = data[4];
+
+ if (file) {
+ // Rewrap the ArrayBuffer in a stream.
+ var fontFileDict = new Dict();
+ file = new Stream(file, 0, file.length, fontFileDict);
+ }
+
+ // At this point, only the font object is created but the font is
+ // not yet attached to the DOM. This is done in `FontLoader.bind`.
+ var font = new Font(name, file, properties);
+ this.objs.resolve(id, font);
+ break;
+ default:
+ error('Got unkown object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('PageError', function transportError(data) {
+ var page = this.pageCache[data.pageNum - 1];
+ if (page.displayReadyPromise)
+ page.displayReadyPromise.reject(data.error);
+ else
+ error(data.error);
+ }, this);
+
+ messageHandler.on('JpegDecode', function(data, promise) {
+ var imageData = data[0];
+ var components = data[1];
+ if (components != 3 && components != 1)
+ error('Only 3 component or 1 component can be returned');
+
+ var img = new Image();
+ img.onload = (function messageHandler_onloadClosure() {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+
+ if (components == 3) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components == 1) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ promise.resolve({ data: buf, width: width, height: height});
+ }).bind(this);
+ var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+ img.src = src;
+ });
+ },
+
+ sendData: function WorkerTransport_sendData(data, params) {
+ this.messageHandler.send('GetDocRequest', {data: data, params: params});
+ },
+
+ getData: function WorkerTransport_sendData(promise) {
+ this.messageHandler.send('GetData', null, function(data) {
+ promise.resolve(data);
+ });
+ },
+
+ getPage: function WorkerTransport_getPage(pageNumber, promise) {
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises)
+ return this.pagePromises[pageIndex];
+ var promise = new PDFJS.Promise('Page ' + pageNumber);
+ this.pagePromises[pageIndex] = promise;
+ this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
+ return promise;
+ },
+
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+ this.messageHandler.send('GetAnnotationsRequest',
+ { pageIndex: pageIndex });
+ }
+ };
+ return WorkerTransport;
+
+})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -1604,27 +2047,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
'shadingFill': true
},
- beginDrawing: function CanvasGraphics_beginDrawing(mediaBox) {
- var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
+ beginDrawing: function CanvasGraphics_beginDrawing(viewport) {
+ var transform = viewport.transform;
this.ctx.save();
- switch (mediaBox.rotate) {
- case 0:
- this.ctx.transform(1, 0, 0, -1, 0, ch);
- break;
- case 90:
- this.ctx.transform(0, 1, 1, 0, 0, 0);
- break;
- case 180:
- this.ctx.transform(-1, 0, 0, 1, cw, 0);
- break;
- case 270:
- this.ctx.transform(0, -1, -1, 0, cw, ch);
- break;
- }
- // Scale so that canvas units are the same as PDF user space units
- this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
- // Move the media left-top corner to the (0,0) canvas position
- this.ctx.translate(-mediaBox.x, -mediaBox.y);
+ this.ctx.transform.apply(this.ctx, transform);
if (this.textLayer)
this.textLayer.beginLayout();
@@ -1723,10 +2149,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.ctx.webkitLineDashOffset = dashPhase;
},
setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
- TODO('set rendering intent: ' + intent);
+ // Maybe if we one day fully support color spaces this will be important
+ // for now we can ignore.
+ // TODO set rendering intent?
},
setFlatness: function CanvasGraphics_setFlatness(flatness) {
- TODO('set flatness: ' + flatness);
+ // There's no way to control this with canvas, but we can safely ignore.
+ // TODO set flatness?
},
setGState: function CanvasGraphics_setGState(states) {
for (var i = 0, ii = states.length; i < ii; i++) {
@@ -2221,7 +2650,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
text.length += shownText.length;
}
} else {
- malformed('TJ array element ' + e + ' is not string or num');
+ error('TJ array element ' + e + ' is not string or num');
}
}
@@ -2474,6 +2903,40 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
}
}
+ function rescaleImage(pixels, widthScale, heightScale) {
+ var scaledWidth = Math.ceil(width / widthScale);
+ var scaledHeight = Math.ceil(height / heightScale);
+
+ var itemsSum = new Uint32Array(scaledWidth * scaledHeight * 4);
+ var itemsCount = new Uint32Array(scaledWidth * scaledHeight);
+ for (var i = 0, position = 0; i < height; i++) {
+ var lineOffset = (0 | (i / heightScale)) * scaledWidth;
+ for (var j = 0; j < width; j++) {
+ var countOffset = lineOffset + (0 | (j / widthScale));
+ var sumOffset = countOffset << 2;
+ itemsSum[sumOffset] += pixels[position];
+ itemsSum[sumOffset + 1] += pixels[position + 1];
+ itemsSum[sumOffset + 2] += pixels[position + 2];
+ itemsSum[sumOffset + 3] += pixels[position + 3];
+ itemsCount[countOffset]++;
+ position += 4;
+ }
+ }
+ var tmpCanvas = createScratchCanvas(scaledWidth, scaledHeight);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var imgData = tmpCtx.getImageData(0, 0, scaledWidth, scaledHeight);
+ pixels = imgData.data;
+ for (var i = 0, j = 0, ii = scaledWidth * scaledHeight; i < ii; i++) {
+ var count = itemsCount[i];
+ pixels[j] = itemsSum[j] / count;
+ pixels[j + 1] = itemsSum[j + 1] / count;
+ pixels[j + 2] = itemsSum[j + 2] / count;
+ pixels[j + 3] = itemsSum[j + 3] / count;
+ j += 4;
+ }
+ tmpCtx.putImageData(imgData, 0, 0);
+ return tmpCanvas;
+ }
this.save();
@@ -2496,8 +2959,19 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
applyStencilMask(pixels, inverseDecode);
- tmpCtx.putImageData(imgData, 0, 0);
- ctx.drawImage(tmpCanvas, 0, -h);
+ var currentTransform = ctx.mozCurrentTransformInverse;
+ var widthScale = Math.max(Math.abs(currentTransform[0]), 1);
+ var heightScale = Math.max(Math.abs(currentTransform[3]), 1);
+ if (widthScale >= 2 || heightScale >= 2) {
+ // canvas does not resize well large images to small -- using simple
+ // algorithm to perform pre-scaling
+ tmpCanvas = rescaleImage(imgData.data, widthScale, heightScale);
+ ctx.scale(widthScale, heightScale);
+ ctx.drawImage(tmpCanvas, 0, -h / heightScale);
+ } else {
+ tmpCtx.putImageData(imgData, 0, 0);
+ ctx.drawImage(tmpCanvas, 0, -h);
+ }
this.restore();
},
@@ -2624,6 +3098,7 @@ if (!isWorker) {
};
}
}
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -2663,51 +3138,55 @@ var Dict = (function DictClosure() {
// 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;
- }
+ var map = Object.create(null);
+
+ this.assignXref = function Dict_assignXref(newXref) {
+ xref = newXref;
+ };
- Dict.prototype = {
// automatically dereferences Ref objects
- get: function Dict_get(key1, key2, key3) {
+ this.get = function Dict_get(key1, key2, key3) {
var value;
- var xref = this.xref;
- if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
+ if (typeof (value = map[key1]) != 'undefined' || key1 in map ||
typeof key2 == 'undefined') {
- return xref ? this.xref.fetchIfRef(value) : value;
+ return xref ? xref.fetchIfRef(value) : value;
}
- if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map ||
+ if (typeof (value = map[key2]) != 'undefined' || key2 in map ||
typeof key3 == 'undefined') {
- return xref ? this.xref.fetchIfRef(value) : value;
+ return xref ? xref.fetchIfRef(value) : value;
}
- value = this.map[key3] || null;
- return xref ? this.xref.fetchIfRef(value) : value;
- },
+ value = map[key3] || null;
+ return xref ? xref.fetchIfRef(value) : value;
+ };
+
// no dereferencing
- getRaw: function Dict_getRaw(key) {
- return this.map[key];
- },
+ this.getRaw = function Dict_getRaw(key) {
+ return map[key];
+ };
+
// creates new map and dereferences all Refs
- getAll: function Dict_getAll() {
+ this.getAll = function Dict_getAll() {
var all = {};
- for (var key in this.map)
- all[key] = this.get(key);
+ for (var key in map) {
+ var obj = this.get(key);
+ all[key] = obj instanceof Dict ? obj.getAll() : obj;
+ }
return all;
- },
+ };
- set: function Dict_set(key, value) {
- this.map[key] = value;
- },
+ this.set = function Dict_set(key, value) {
+ map[key] = value;
+ };
- has: function Dict_has(key) {
- return key in this.map;
- },
+ this.has = function Dict_has(key) {
+ return key in map;
+ };
- forEach: function Dict_forEach(callback) {
- for (var key in this.map) {
+ this.forEach = function Dict_forEach(callback) {
+ for (var key in map) {
callback(key, this.get(key));
}
- }
+ };
};
return Dict;
@@ -2754,7 +3233,14 @@ var Catalog = (function CatalogClosure() {
Catalog.prototype = {
get metadata() {
- var stream = this.catDict.get('Metadata');
+ var streamRef = this.catDict.getRaw('Metadata');
+ if (!isRef(streamRef))
+ return shadow(this, 'metadata', null);
+
+ var encryptMetadata = !this.xref.encrypt ? false :
+ this.xref.encrypt.encryptMetadata;
+
+ var stream = this.xref.fetch(streamRef, !encryptMetadata);
var metadata;
if (stream && isDict(stream.dict)) {
var type = stream.dict.get('Type');
@@ -2762,7 +3248,16 @@ var Catalog = (function CatalogClosure() {
if (isName(type) && isName(subtype) &&
type.name === 'Metadata' && subtype.name === 'XML') {
- metadata = stringToPDFString(bytesToString(stream.getBytes()));
+ // XXX: This should examine the charset the XML document defines,
+ // however since there are currently no real means to decode
+ // arbitrary charsets, let's just hope that the author of the PDF
+ // was reasonable enough to stick with the XML default charset,
+ // which is UTF-8.
+ try {
+ metadata = stringToUTF8String(bytesToString(stream.getBytes()));
+ } catch (e) {
+ info('Skipping invalid metadata.');
+ }
}
}
@@ -2920,12 +3415,12 @@ var Catalog = (function CatalogClosure() {
})();
var XRef = (function XRefClosure() {
- function XRef(stream, startXRef, mainXRefEntriesOffset) {
+ function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
- trailerDict.xref = this;
+ trailerDict.assignXref(this);
this.trailer = trailerDict;
// prepare the XRef cache
this.cache = [];
@@ -2933,8 +3428,7 @@ var XRef = (function XRefClosure() {
var encrypt = trailerDict.get('Encrypt');
if (encrypt) {
var fileId = trailerDict.get('ID');
- this.encrypt = new CipherTransformFactory(encrypt,
- fileId[0] /*, password */);
+ this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password);
}
// get the root dictionary (catalog) object
@@ -2986,9 +3480,8 @@ var XRef = (function XRefClosure() {
}
}
- // Sanity check: as per spec, first object must have these properties
- if (this.entries[0] &&
- !(this.entries[0].gen === 65535 && this.entries[0].free))
+ // Sanity check: as per spec, first object must be free
+ if (this.entries[0] && !this.entries[0].free)
error('Invalid XRef table: unexpected first object');
// Sanity check
@@ -3147,7 +3640,7 @@ var XRef = (function XRefClosure() {
}
// reading XRef streams
for (var i = 0, ii = xrefStms.length; i < ii; ++i) {
- this.readXRef(xrefStms[i]);
+ this.readXRef(xrefStms[i], true);
}
// finding main trailer
var dict;
@@ -3170,7 +3663,7 @@ var XRef = (function XRefClosure() {
// nothing helps
error('Invalid PDF structure');
},
- readXRef: function XRef_readXRef(startXRef) {
+ readXRef: function XRef_readXRef(startXRef, recoveryMode) {
var stream = this.stream;
stream.pos = startXRef;
@@ -3203,16 +3696,18 @@ var XRef = (function XRefClosure() {
error('Invalid XRef stream');
}
dict = this.readXRefStream(obj);
+ if (!dict)
+ error('Failed to read XRef stream');
}
// Recursively get previous dictionary, if any
obj = dict.get('Prev');
if (isInt(obj))
- this.readXRef(obj);
+ this.readXRef(obj, recoveryMode);
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);
+ this.readXRef(obj.num, recoveryMode);
}
return dict;
@@ -3220,6 +3715,9 @@ var XRef = (function XRefClosure() {
log('(while reading XRef): ' + e);
}
+ if (recoveryMode)
+ return;
+
warn('Indexing all PDF objects');
return this.indexObjects();
},
@@ -3432,6 +3930,7 @@ var PDFObjects = (function PDFObjectsClosure() {
return PDFObjects;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -4270,7 +4769,10 @@ var PostScriptLexer = (function PostScriptLexerClosure() {
// operator
var str = ch.toLowerCase();
while (true) {
- ch = stream.lookChar().toLowerCase();
+ ch = stream.lookChar();
+ if (ch === null)
+ break;
+ ch = ch.toLowerCase();
if (ch >= 'a' && ch <= 'z')
str += ch;
else
@@ -4306,6 +4808,7 @@ var PostScriptLexer = (function PostScriptLexerClosure() {
return PostScriptLexer;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -4411,6 +4914,7 @@ var ExpertSubsetCharset = [
'periodinferior', 'commainferior'
];
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -11344,6 +11848,7 @@ var CIDToUnicodeMaps = {
{f: 7, c: 19887}]
};
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -11797,12 +12302,12 @@ var LabCS = (function LabCSClosure() {
error('Invalid WhitePoint components, no fallback available');
if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
- warn('Invalid BlackPoint, falling back to default');
+ info('Invalid BlackPoint, falling back to default');
this.XB = this.YB = this.ZB = 0;
}
if (this.amin > this.amax || this.bmin > this.bmax) {
- warn('Invalid Range, falling back to defaults');
+ info('Invalid Range, falling back to defaults');
this.amin = -100;
this.amax = 100;
this.bmin = -100;
@@ -11876,6 +12381,7 @@ var LabCS = (function LabCSClosure() {
};
return LabCS;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -12297,13 +12803,14 @@ var CipherTransform = (function CipherTransformClosure() {
})();
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
+ var defaultPasswordBytes = new Uint8Array([
+ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
+ 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
+ 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
+
function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) {
- var defaultPasswordBytes = new Uint8Array([
- 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
- 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
- 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
- 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
var hashData = new Uint8Array(100), i = 0, j, n;
if (password) {
n = Math.min(32, password.length);
@@ -12340,9 +12847,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var cipher, checkData;
if (revision >= 3) {
- // padded password in hashData, we can use this array for user
- // password check
- i = 32;
+ for (i = 0; i < 32; ++i)
+ hashData[i] = defaultPasswordBytes[i];
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
@@ -12355,16 +12861,53 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
}
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] != checkData[j])
+ return null;
+ }
} else {
cipher = new ARCFourCipher(encryptionKey);
- checkData = cipher.encryptBlock(hashData.subarray(0, 32));
- }
- for (j = 0, n = checkData.length; j < n; ++j) {
- if (userPassword[j] != checkData[j])
- error('incorrect password');
+ checkData = cipher.encryptBlock(defaultPasswordBytes);
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] != checkData[j])
+ return null;
+ }
}
return encryptionKey;
}
+ function decodeUserPassword(password, ownerPassword, revision, keyLength) {
+ var hashData = new Uint8Array(32), i = 0, j, n;
+ n = Math.min(32, password.length);
+ for (; i < n; ++i)
+ hashData[i] = password[i];
+ j = 0;
+ while (i < 32) {
+ hashData[i++] = defaultPasswordBytes[j++];
+ }
+ var hash = calculateMD5(hashData, 0, i);
+ var keyLengthInBytes = keyLength >> 3;
+ if (revision >= 3) {
+ for (j = 0; j < 50; ++j) {
+ hash = calculateMD5(hash, 0, hash.length);
+ }
+ }
+
+ var cipher, userPassword;
+ if (revision >= 3) {
+ userPassword = ownerPassword;
+ var derivedKey = new Uint8Array(keyLengthInBytes), k;
+ for (j = 19; j >= 0; j--) {
+ for (k = 0; k < keyLengthInBytes; ++k)
+ derivedKey[k] = hash[k] ^ j;
+ cipher = new ARCFourCipher(derivedKey);
+ userPassword = cipher.encryptBlock(userPassword);
+ }
+ } else {
+ cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
+ userPassword = cipher.encryptBlock(ownerPassword);
+ }
+ return userPassword;
+ }
var identityName = new Name('Identity');
@@ -12387,17 +12930,34 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var userPassword = stringToBytes(dict.get('U'));
var flags = dict.get('P');
var revision = dict.get('R');
- var encryptMetadata =
+ var encryptMetadata = algorithm == 4 && // meaningful when V is 4
dict.get('EncryptMetadata') !== false; // makes true as default value
+ this.encryptMetadata = encryptMetadata;
+
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password)
passwordBytes = stringToBytes(password);
- this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
- ownerPassword, userPassword,
- flags, revision,
- keyLength, encryptMetadata);
+ var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
+ ownerPassword, userPassword, flags,
+ revision, keyLength, encryptMetadata);
+ if (!encryptionKey && !password) {
+ throw new PasswordException('No password given', 'needpassword');
+ } else if (!encryptionKey && password) {
+ // Attempting use the password as an owner password
+ var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
+ revision, keyLength);
+ encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
+ ownerPassword, userPassword, flags,
+ revision, keyLength, encryptMetadata);
+ }
+
+ if (!encryptionKey)
+ throw new PasswordException('Incorrect Password', 'incorrectpassword');
+
+ this.encryptionKey = encryptionKey;
+
if (algorithm == 4) {
this.cf = dict.get('CF');
this.stmf = dict.get('StmF') || identityName;
@@ -12472,6 +13032,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() {
return CipherTransformFactory;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -12582,20 +13143,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// Compatibility
BX: 'beginCompat',
- EX: 'endCompat'
+ EX: 'endCompat',
+
+ // (reserved partial commands for the lexer)
+ BM: null,
+ BD: null,
+ 'true': null,
+ fa: null,
+ fal: null,
+ fals: null,
+ 'false': null,
+ nu: null,
+ nul: null,
+ 'null': null
};
- function splitCombinedOperations(operations) {
- // Two operations can be combined together, trying to find which two
- // operations were concatenated.
- for (var i = operations.length - 1; i > 0; i--) {
- var op1 = operations.substring(0, i), op2 = operations.substring(i);
- if (op1 in OP_MAP && op2 in OP_MAP)
- return [op1, op2]; // operations found
- }
- return null;
- }
-
PartialEvaluator.prototype = {
getOperatorList: function PartialEvaluator_getOperatorList(stream,
resources,
@@ -12627,13 +13189,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
font = xref.fetchIfRef(font) || fontRes.get(fontName);
assertWellFormed(isDict(font));
- if (!font.translated) {
+
+ ++self.objIdCounter;
+ if (!font.loadedName) {
font.translated = self.translateFont(font, xref, resources,
dependency);
if (font.translated) {
// keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page
- loadedName = 'font_' + uniquePrefix + (++self.objIdCounter);
+ loadedName = 'font_' + uniquePrefix + self.objIdCounter;
font.translated.properties.loadedName = loadedName;
font.loadedName = loadedName;
@@ -12738,36 +13302,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
resources = resources || new Dict();
var xobjs = resources.get('XObject') || new Dict();
var patterns = resources.get('Pattern') || new Dict();
- var parser = new Parser(new Lexer(stream), false, xref);
+ var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
var res = resources;
- var hasNextObj = false, nextObj;
var args = [], obj;
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
while (true) {
- if (hasNextObj) {
- obj = nextObj;
- hasNextObj = false;
- } else {
- obj = parser.getObj();
- if (isEOF(obj))
- break;
- }
+ obj = parser.getObj();
+ if (isEOF(obj))
+ break;
if (isCmd(obj)) {
var cmd = obj.cmd;
var fn = OP_MAP[cmd];
- if (!fn) {
- // invalid content command, trying to recover
- var cmds = splitCombinedOperations(cmd);
- if (cmds) {
- cmd = cmds[0];
- fn = OP_MAP[cmd];
- // feeding other command on the next interation
- hasNextObj = true;
- nextObj = Cmd.get(cmds[1]);
- }
- }
assertWellFormed(fn, 'Unknown command "' + cmd + '"');
// TODO figure out how to type-check vararg functions
@@ -12907,6 +13454,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
value[1]
]);
break;
+ case 'BM':
+ // We support the default so don't trigger the TODO.
+ if (!isName(value) || value.name != 'Normal')
+ TODO('graphic state operator ' + key);
+ break;
+ case 'SMask':
+ // We support the default so don't trigger the TODO.
+ if (!isName(value) || value.name != 'None')
+ TODO('graphic state operator ' + key);
+ break;
+ // Only generate info log messages for the following since
+ // they are unlikey to have a big impact on the rendering.
case 'OP':
case 'op':
case 'OPM':
@@ -12919,14 +13478,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case 'HT':
case 'SM':
case 'SA':
- case 'BM':
- case 'SMask':
case 'AIS':
case 'TK':
- TODO('graphic state operator ' + key);
+ // TODO implement these operators.
+ info('graphic state operator ' + key);
break;
default:
- warn('Unknown graphic state operator ' + key);
+ info('Unknown graphic state operator ' + key);
break;
}
}
@@ -12940,13 +13498,88 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
args = [];
} else if (obj != null) {
assertWellFormed(args.length <= 33, 'Too many arguments');
- args.push(obj);
+ args.push(obj instanceof Dict ? obj.getAll() : obj);
}
}
return queue;
},
+ getTextContent: function partialEvaluatorGetIRQueue(stream, resources) {
+
+ var self = this;
+ var xref = this.xref;
+
+ function handleSetFont(fontName, fontRef) {
+ var fontRes = resources.get('Font');
+
+ // TODO: TOASK: Is it possible to get here? If so, what does
+ // args[0].name should be like???
+ assert(fontRes, 'fontRes not available');
+
+ fontRes = xref.fetchIfRef(fontRes);
+ fontRef = fontRef || fontRes.get(fontName);
+ var font = xref.fetchIfRef(fontRef), tra;
+ assertWellFormed(isDict(font));
+ if (!font.translated) {
+ font.translated = self.translateFont(font, xref, resources);
+ }
+ return font;
+ }
+
+ resources = xref.fetchIfRef(resources) || new Dict();
+
+ var parser = new Parser(new Lexer(stream), false);
+ var res = resources;
+ var args = [], obj;
+
+ var text = '';
+ var chunk = '';
+ var font = null;
+ while (!isEOF(obj = parser.getObj())) {
+ if (isCmd(obj)) {
+ var cmd = obj.cmd;
+ switch (cmd) {
+ case 'Tf':
+ font = handleSetFont(args[0].name);
+ break;
+ case 'TJ':
+ var items = args[0];
+ for (var j = 0, jj = items.length; j < jj; j++) {
+ if (typeof items[j] === 'string') {
+ chunk += items[j];
+ } else if (items[j] < 0) {
+ // making all negative offsets a space - better to have
+ // a space in incorrect place than not have them at all
+ chunk += ' ';
+ }
+ }
+ break;
+ case 'Tj':
+ chunk += args[0];
+ break;
+ case "'":
+ chunk += args[0] + ' ';
+ break;
+ case '"':
+ chunk += args[2] + ' ';
+ break;
+ } // switch
+ if (chunk !== '') {
+ text += fontCharsToUnicode(chunk, font.translated.properties);
+ chunk = '';
+ }
+
+ args = [];
+ } else if (obj != null) {
+ assertWellFormed(args.length <= 33, 'Too many arguments');
+ args.push(obj);
+ }
+ }
+
+ return text;
+ },
+
extractDataStructures: function
partialEvaluatorExtractDataStructures(dict, baseDict,
xref, properties) {
@@ -12954,7 +13587,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var toUnicode = dict.get('ToUnicode') ||
baseDict.get('ToUnicode');
if (toUnicode)
- properties.toUnicode = this.readToUnicode(toUnicode, xref);
+ properties.toUnicode = this.readToUnicode(toUnicode, xref, properties);
if (properties.composite) {
// CIDSystemInfo helps to match CID to glyphs
@@ -13010,7 +13643,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties.hasEncoding = hasEncoding;
},
- readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref) {
+ readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref,
+ properties) {
var cmapObj = toUnicode;
var charToUnicode = [];
if (isName(cmapObj)) {
@@ -13099,6 +13733,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
} else if (octet == 0x3E) {
if (token.length) {
+ // Heuristic: guessing chars size by checking numbers sizes
+ // in the CMap entries.
+ if (token.length == 2 && properties.composite)
+ properties.wideChars = false;
+
if (token.length <= 4) {
// parsing hex number
tokens.push(parseInt(token, 16));
@@ -13316,6 +13955,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
length1: length1,
length2: length2,
composite: composite,
+ wideChars: composite,
fixedPitch: false,
fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX,
firstChar: firstChar || 0,
@@ -13336,7 +13976,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties.coded = true;
var charProcs = dict.get('CharProcs').getAll();
var fontResources = dict.get('Resources') || resources;
- properties.resources = fontResources;
properties.charProcOperatorList = {};
for (var key in charProcs) {
var glyphStream = charProcs[key];
@@ -13380,6 +14019,7 @@ var EvalState = (function EvalStateClosure() {
return EvalState;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -14135,6 +14775,736 @@ function isSpecialUnicode(unicode) {
unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
}
+// The normalization table is obtained by filtering the Unicode characters
+// database with <compat> entries.
+var NormalizedUnicodes = {
+ '\u00A8': '\u0020\u0308',
+ '\u00AF': '\u0020\u0304',
+ '\u00B4': '\u0020\u0301',
+ '\u00B5': '\u03BC',
+ '\u00B8': '\u0020\u0327',
+ '\u0132': '\u0049\u004A',
+ '\u0133': '\u0069\u006A',
+ '\u013F': '\u004C\u00B7',
+ '\u0140': '\u006C\u00B7',
+ '\u0149': '\u02BC\u006E',
+ '\u017F': '\u0073',
+ '\u01C4': '\u0044\u017D',
+ '\u01C5': '\u0044\u017E',
+ '\u01C6': '\u0064\u017E',
+ '\u01C7': '\u004C\u004A',
+ '\u01C8': '\u004C\u006A',
+ '\u01C9': '\u006C\u006A',
+ '\u01CA': '\u004E\u004A',
+ '\u01CB': '\u004E\u006A',
+ '\u01CC': '\u006E\u006A',
+ '\u01F1': '\u0044\u005A',
+ '\u01F2': '\u0044\u007A',
+ '\u01F3': '\u0064\u007A',
+ '\u02D8': '\u0020\u0306',
+ '\u02D9': '\u0020\u0307',
+ '\u02DA': '\u0020\u030A',
+ '\u02DB': '\u0020\u0328',
+ '\u02DC': '\u0020\u0303',
+ '\u02DD': '\u0020\u030B',
+ '\u037A': '\u0020\u0345',
+ '\u0384': '\u0020\u0301',
+ '\u03D0': '\u03B2',
+ '\u03D1': '\u03B8',
+ '\u03D2': '\u03A5',
+ '\u03D5': '\u03C6',
+ '\u03D6': '\u03C0',
+ '\u03F0': '\u03BA',
+ '\u03F1': '\u03C1',
+ '\u03F2': '\u03C2',
+ '\u03F4': '\u0398',
+ '\u03F5': '\u03B5',
+ '\u03F9': '\u03A3',
+ '\u0587': '\u0565\u0582',
+ '\u0675': '\u0627\u0674',
+ '\u0676': '\u0648\u0674',
+ '\u0677': '\u06C7\u0674',
+ '\u0678': '\u064A\u0674',
+ '\u0E33': '\u0E4D\u0E32',
+ '\u0EB3': '\u0ECD\u0EB2',
+ '\u0EDC': '\u0EAB\u0E99',
+ '\u0EDD': '\u0EAB\u0EA1',
+ '\u0F77': '\u0FB2\u0F81',
+ '\u0F79': '\u0FB3\u0F81',
+ '\u1E9A': '\u0061\u02BE',
+ '\u1FBD': '\u0020\u0313',
+ '\u1FBF': '\u0020\u0313',
+ '\u1FC0': '\u0020\u0342',
+ '\u1FFE': '\u0020\u0314',
+ '\u2002': '\u0020',
+ '\u2003': '\u0020',
+ '\u2004': '\u0020',
+ '\u2005': '\u0020',
+ '\u2006': '\u0020',
+ '\u2008': '\u0020',
+ '\u2009': '\u0020',
+ '\u200A': '\u0020',
+ '\u2017': '\u0020\u0333',
+ '\u2024': '\u002E',
+ '\u2025': '\u002E\u002E',
+ '\u2026': '\u002E\u002E\u002E',
+ '\u2033': '\u2032\u2032',
+ '\u2034': '\u2032\u2032\u2032',
+ '\u2036': '\u2035\u2035',
+ '\u2037': '\u2035\u2035\u2035',
+ '\u203C': '\u0021\u0021',
+ '\u203E': '\u0020\u0305',
+ '\u2047': '\u003F\u003F',
+ '\u2048': '\u003F\u0021',
+ '\u2049': '\u0021\u003F',
+ '\u2057': '\u2032\u2032\u2032\u2032',
+ '\u205F': '\u0020',
+ '\u20A8': '\u0052\u0073',
+ '\u2100': '\u0061\u002F\u0063',
+ '\u2101': '\u0061\u002F\u0073',
+ '\u2103': '\u00B0\u0043',
+ '\u2105': '\u0063\u002F\u006F',
+ '\u2106': '\u0063\u002F\u0075',
+ '\u2107': '\u0190',
+ '\u2109': '\u00B0\u0046',
+ '\u2116': '\u004E\u006F',
+ '\u2121': '\u0054\u0045\u004C',
+ '\u2135': '\u05D0',
+ '\u2136': '\u05D1',
+ '\u2137': '\u05D2',
+ '\u2138': '\u05D3',
+ '\u213B': '\u0046\u0041\u0058',
+ '\u2160': '\u0049',
+ '\u2161': '\u0049\u0049',
+ '\u2162': '\u0049\u0049\u0049',
+ '\u2163': '\u0049\u0056',
+ '\u2164': '\u0056',
+ '\u2165': '\u0056\u0049',
+ '\u2166': '\u0056\u0049\u0049',
+ '\u2167': '\u0056\u0049\u0049\u0049',
+ '\u2168': '\u0049\u0058',
+ '\u2169': '\u0058',
+ '\u216A': '\u0058\u0049',
+ '\u216B': '\u0058\u0049\u0049',
+ '\u216C': '\u004C',
+ '\u216D': '\u0043',
+ '\u216E': '\u0044',
+ '\u216F': '\u004D',
+ '\u2170': '\u0069',
+ '\u2171': '\u0069\u0069',
+ '\u2172': '\u0069\u0069\u0069',
+ '\u2173': '\u0069\u0076',
+ '\u2174': '\u0076',
+ '\u2175': '\u0076\u0069',
+ '\u2176': '\u0076\u0069\u0069',
+ '\u2177': '\u0076\u0069\u0069\u0069',
+ '\u2178': '\u0069\u0078',
+ '\u2179': '\u0078',
+ '\u217A': '\u0078\u0069',
+ '\u217B': '\u0078\u0069\u0069',
+ '\u217C': '\u006C',
+ '\u217D': '\u0063',
+ '\u217E': '\u0064',
+ '\u217F': '\u006D',
+ '\u222C': '\u222B\u222B',
+ '\u222D': '\u222B\u222B\u222B',
+ '\u222F': '\u222E\u222E',
+ '\u2230': '\u222E\u222E\u222E',
+ '\u2474': '\u0028\u0031\u0029',
+ '\u2475': '\u0028\u0032\u0029',
+ '\u2476': '\u0028\u0033\u0029',
+ '\u2477': '\u0028\u0034\u0029',
+ '\u2478': '\u0028\u0035\u0029',
+ '\u2479': '\u0028\u0036\u0029',
+ '\u247A': '\u0028\u0037\u0029',
+ '\u247B': '\u0028\u0038\u0029',
+ '\u247C': '\u0028\u0039\u0029',
+ '\u247D': '\u0028\u0031\u0030\u0029',
+ '\u247E': '\u0028\u0031\u0031\u0029',
+ '\u247F': '\u0028\u0031\u0032\u0029',
+ '\u2480': '\u0028\u0031\u0033\u0029',
+ '\u2481': '\u0028\u0031\u0034\u0029',
+ '\u2482': '\u0028\u0031\u0035\u0029',
+ '\u2483': '\u0028\u0031\u0036\u0029',
+ '\u2484': '\u0028\u0031\u0037\u0029',
+ '\u2485': '\u0028\u0031\u0038\u0029',
+ '\u2486': '\u0028\u0031\u0039\u0029',
+ '\u2487': '\u0028\u0032\u0030\u0029',
+ '\u2488': '\u0031\u002E',
+ '\u2489': '\u0032\u002E',
+ '\u248A': '\u0033\u002E',
+ '\u248B': '\u0034\u002E',
+ '\u248C': '\u0035\u002E',
+ '\u248D': '\u0036\u002E',
+ '\u248E': '\u0037\u002E',
+ '\u248F': '\u0038\u002E',
+ '\u2490': '\u0039\u002E',
+ '\u2491': '\u0031\u0030\u002E',
+ '\u2492': '\u0031\u0031\u002E',
+ '\u2493': '\u0031\u0032\u002E',
+ '\u2494': '\u0031\u0033\u002E',
+ '\u2495': '\u0031\u0034\u002E',
+ '\u2496': '\u0031\u0035\u002E',
+ '\u2497': '\u0031\u0036\u002E',
+ '\u2498': '\u0031\u0037\u002E',
+ '\u2499': '\u0031\u0038\u002E',
+ '\u249A': '\u0031\u0039\u002E',
+ '\u249B': '\u0032\u0030\u002E',
+ '\u249C': '\u0028\u0061\u0029',
+ '\u249D': '\u0028\u0062\u0029',
+ '\u249E': '\u0028\u0063\u0029',
+ '\u249F': '\u0028\u0064\u0029',
+ '\u24A0': '\u0028\u0065\u0029',
+ '\u24A1': '\u0028\u0066\u0029',
+ '\u24A2': '\u0028\u0067\u0029',
+ '\u24A3': '\u0028\u0068\u0029',
+ '\u24A4': '\u0028\u0069\u0029',
+ '\u24A5': '\u0028\u006A\u0029',
+ '\u24A6': '\u0028\u006B\u0029',
+ '\u24A7': '\u0028\u006C\u0029',
+ '\u24A8': '\u0028\u006D\u0029',
+ '\u24A9': '\u0028\u006E\u0029',
+ '\u24AA': '\u0028\u006F\u0029',
+ '\u24AB': '\u0028\u0070\u0029',
+ '\u24AC': '\u0028\u0071\u0029',
+ '\u24AD': '\u0028\u0072\u0029',
+ '\u24AE': '\u0028\u0073\u0029',
+ '\u24AF': '\u0028\u0074\u0029',
+ '\u24B0': '\u0028\u0075\u0029',
+ '\u24B1': '\u0028\u0076\u0029',
+ '\u24B2': '\u0028\u0077\u0029',
+ '\u24B3': '\u0028\u0078\u0029',
+ '\u24B4': '\u0028\u0079\u0029',
+ '\u24B5': '\u0028\u007A\u0029',
+ '\u2A0C': '\u222B\u222B\u222B\u222B',
+ '\u2A74': '\u003A\u003A\u003D',
+ '\u2A75': '\u003D\u003D',
+ '\u2A76': '\u003D\u003D\u003D',
+ '\u2E9F': '\u6BCD',
+ '\u2EF3': '\u9F9F',
+ '\u2F00': '\u4E00',
+ '\u2F01': '\u4E28',
+ '\u2F02': '\u4E36',
+ '\u2F03': '\u4E3F',
+ '\u2F04': '\u4E59',
+ '\u2F05': '\u4E85',
+ '\u2F06': '\u4E8C',
+ '\u2F07': '\u4EA0',
+ '\u2F08': '\u4EBA',
+ '\u2F09': '\u513F',
+ '\u2F0A': '\u5165',
+ '\u2F0B': '\u516B',
+ '\u2F0C': '\u5182',
+ '\u2F0D': '\u5196',
+ '\u2F0E': '\u51AB',
+ '\u2F0F': '\u51E0',
+ '\u2F10': '\u51F5',
+ '\u2F11': '\u5200',
+ '\u2F12': '\u529B',
+ '\u2F13': '\u52F9',
+ '\u2F14': '\u5315',
+ '\u2F15': '\u531A',
+ '\u2F16': '\u5338',
+ '\u2F17': '\u5341',
+ '\u2F18': '\u535C',
+ '\u2F19': '\u5369',
+ '\u2F1A': '\u5382',
+ '\u2F1B': '\u53B6',
+ '\u2F1C': '\u53C8',
+ '\u2F1D': '\u53E3',
+ '\u2F1E': '\u56D7',
+ '\u2F1F': '\u571F',
+ '\u2F20': '\u58EB',
+ '\u2F21': '\u5902',
+ '\u2F22': '\u590A',
+ '\u2F23': '\u5915',
+ '\u2F24': '\u5927',
+ '\u2F25': '\u5973',
+ '\u2F26': '\u5B50',
+ '\u2F27': '\u5B80',
+ '\u2F28': '\u5BF8',
+ '\u2F29': '\u5C0F',
+ '\u2F2A': '\u5C22',
+ '\u2F2B': '\u5C38',
+ '\u2F2C': '\u5C6E',
+ '\u2F2D': '\u5C71',
+ '\u2F2E': '\u5DDB',
+ '\u2F2F': '\u5DE5',
+ '\u2F30': '\u5DF1',
+ '\u2F31': '\u5DFE',
+ '\u2F32': '\u5E72',
+ '\u2F33': '\u5E7A',
+ '\u2F34': '\u5E7F',
+ '\u2F35': '\u5EF4',
+ '\u2F36': '\u5EFE',
+ '\u2F37': '\u5F0B',
+ '\u2F38': '\u5F13',
+ '\u2F39': '\u5F50',
+ '\u2F3A': '\u5F61',
+ '\u2F3B': '\u5F73',
+ '\u2F3C': '\u5FC3',
+ '\u2F3D': '\u6208',
+ '\u2F3E': '\u6236',
+ '\u2F3F': '\u624B',
+ '\u2F40': '\u652F',
+ '\u2F41': '\u6534',
+ '\u2F42': '\u6587',
+ '\u2F43': '\u6597',
+ '\u2F44': '\u65A4',
+ '\u2F45': '\u65B9',
+ '\u2F46': '\u65E0',
+ '\u2F47': '\u65E5',
+ '\u2F48': '\u66F0',
+ '\u2F49': '\u6708',
+ '\u2F4A': '\u6728',
+ '\u2F4B': '\u6B20',
+ '\u2F4C': '\u6B62',
+ '\u2F4D': '\u6B79',
+ '\u2F4E': '\u6BB3',
+ '\u2F4F': '\u6BCB',
+ '\u2F50': '\u6BD4',
+ '\u2F51': '\u6BDB',
+ '\u2F52': '\u6C0F',
+ '\u2F53': '\u6C14',
+ '\u2F54': '\u6C34',
+ '\u2F55': '\u706B',
+ '\u2F56': '\u722A',
+ '\u2F57': '\u7236',
+ '\u2F58': '\u723B',
+ '\u2F59': '\u723F',
+ '\u2F5A': '\u7247',
+ '\u2F5B': '\u7259',
+ '\u2F5C': '\u725B',
+ '\u2F5D': '\u72AC',
+ '\u2F5E': '\u7384',
+ '\u2F5F': '\u7389',
+ '\u2F60': '\u74DC',
+ '\u2F61': '\u74E6',
+ '\u2F62': '\u7518',
+ '\u2F63': '\u751F',
+ '\u2F64': '\u7528',
+ '\u2F65': '\u7530',
+ '\u2F66': '\u758B',
+ '\u2F67': '\u7592',
+ '\u2F68': '\u7676',
+ '\u2F69': '\u767D',
+ '\u2F6A': '\u76AE',
+ '\u2F6B': '\u76BF',
+ '\u2F6C': '\u76EE',
+ '\u2F6D': '\u77DB',
+ '\u2F6E': '\u77E2',
+ '\u2F6F': '\u77F3',
+ '\u2F70': '\u793A',
+ '\u2F71': '\u79B8',
+ '\u2F72': '\u79BE',
+ '\u2F73': '\u7A74',
+ '\u2F74': '\u7ACB',
+ '\u2F75': '\u7AF9',
+ '\u2F76': '\u7C73',
+ '\u2F77': '\u7CF8',
+ '\u2F78': '\u7F36',
+ '\u2F79': '\u7F51',
+ '\u2F7A': '\u7F8A',
+ '\u2F7B': '\u7FBD',
+ '\u2F7C': '\u8001',
+ '\u2F7D': '\u800C',
+ '\u2F7E': '\u8012',
+ '\u2F7F': '\u8033',
+ '\u2F80': '\u807F',
+ '\u2F81': '\u8089',
+ '\u2F82': '\u81E3',
+ '\u2F83': '\u81EA',
+ '\u2F84': '\u81F3',
+ '\u2F85': '\u81FC',
+ '\u2F86': '\u820C',
+ '\u2F87': '\u821B',
+ '\u2F88': '\u821F',
+ '\u2F89': '\u826E',
+ '\u2F8A': '\u8272',
+ '\u2F8B': '\u8278',
+ '\u2F8C': '\u864D',
+ '\u2F8D': '\u866B',
+ '\u2F8E': '\u8840',
+ '\u2F8F': '\u884C',
+ '\u2F90': '\u8863',
+ '\u2F91': '\u897E',
+ '\u2F92': '\u898B',
+ '\u2F93': '\u89D2',
+ '\u2F94': '\u8A00',
+ '\u2F95': '\u8C37',
+ '\u2F96': '\u8C46',
+ '\u2F97': '\u8C55',
+ '\u2F98': '\u8C78',
+ '\u2F99': '\u8C9D',
+ '\u2F9A': '\u8D64',
+ '\u2F9B': '\u8D70',
+ '\u2F9C': '\u8DB3',
+ '\u2F9D': '\u8EAB',
+ '\u2F9E': '\u8ECA',
+ '\u2F9F': '\u8F9B',
+ '\u2FA0': '\u8FB0',
+ '\u2FA1': '\u8FB5',
+ '\u2FA2': '\u9091',
+ '\u2FA3': '\u9149',
+ '\u2FA4': '\u91C6',
+ '\u2FA5': '\u91CC',
+ '\u2FA6': '\u91D1',
+ '\u2FA7': '\u9577',
+ '\u2FA8': '\u9580',
+ '\u2FA9': '\u961C',
+ '\u2FAA': '\u96B6',
+ '\u2FAB': '\u96B9',
+ '\u2FAC': '\u96E8',
+ '\u2FAD': '\u9751',
+ '\u2FAE': '\u975E',
+ '\u2FAF': '\u9762',
+ '\u2FB0': '\u9769',
+ '\u2FB1': '\u97CB',
+ '\u2FB2': '\u97ED',
+ '\u2FB3': '\u97F3',
+ '\u2FB4': '\u9801',
+ '\u2FB5': '\u98A8',
+ '\u2FB6': '\u98DB',
+ '\u2FB7': '\u98DF',
+ '\u2FB8': '\u9996',
+ '\u2FB9': '\u9999',
+ '\u2FBA': '\u99AC',
+ '\u2FBB': '\u9AA8',
+ '\u2FBC': '\u9AD8',
+ '\u2FBD': '\u9ADF',
+ '\u2FBE': '\u9B25',
+ '\u2FBF': '\u9B2F',
+ '\u2FC0': '\u9B32',
+ '\u2FC1': '\u9B3C',
+ '\u2FC2': '\u9B5A',
+ '\u2FC3': '\u9CE5',
+ '\u2FC4': '\u9E75',
+ '\u2FC5': '\u9E7F',
+ '\u2FC6': '\u9EA5',
+ '\u2FC7': '\u9EBB',
+ '\u2FC8': '\u9EC3',
+ '\u2FC9': '\u9ECD',
+ '\u2FCA': '\u9ED1',
+ '\u2FCB': '\u9EF9',
+ '\u2FCC': '\u9EFD',
+ '\u2FCD': '\u9F0E',
+ '\u2FCE': '\u9F13',
+ '\u2FCF': '\u9F20',
+ '\u2FD0': '\u9F3B',
+ '\u2FD1': '\u9F4A',
+ '\u2FD2': '\u9F52',
+ '\u2FD3': '\u9F8D',
+ '\u2FD4': '\u9F9C',
+ '\u2FD5': '\u9FA0',
+ '\u3036': '\u3012',
+ '\u3038': '\u5341',
+ '\u3039': '\u5344',
+ '\u303A': '\u5345',
+ '\u309B': '\u0020\u3099',
+ '\u309C': '\u0020\u309A',
+ '\u3131': '\u1100',
+ '\u3132': '\u1101',
+ '\u3133': '\u11AA',
+ '\u3134': '\u1102',
+ '\u3135': '\u11AC',
+ '\u3136': '\u11AD',
+ '\u3137': '\u1103',
+ '\u3138': '\u1104',
+ '\u3139': '\u1105',
+ '\u313A': '\u11B0',
+ '\u313B': '\u11B1',
+ '\u313C': '\u11B2',
+ '\u313D': '\u11B3',
+ '\u313E': '\u11B4',
+ '\u313F': '\u11B5',
+ '\u3140': '\u111A',
+ '\u3141': '\u1106',
+ '\u3142': '\u1107',
+ '\u3143': '\u1108',
+ '\u3144': '\u1121',
+ '\u3145': '\u1109',
+ '\u3146': '\u110A',
+ '\u3147': '\u110B',
+ '\u3148': '\u110C',
+ '\u3149': '\u110D',
+ '\u314A': '\u110E',
+ '\u314B': '\u110F',
+ '\u314C': '\u1110',
+ '\u314D': '\u1111',
+ '\u314E': '\u1112',
+ '\u314F': '\u1161',
+ '\u3150': '\u1162',
+ '\u3151': '\u1163',
+ '\u3152': '\u1164',
+ '\u3153': '\u1165',
+ '\u3154': '\u1166',
+ '\u3155': '\u1167',
+ '\u3156': '\u1168',
+ '\u3157': '\u1169',
+ '\u3158': '\u116A',
+ '\u3159': '\u116B',
+ '\u315A': '\u116C',
+ '\u315B': '\u116D',
+ '\u315C': '\u116E',
+ '\u315D': '\u116F',
+ '\u315E': '\u1170',
+ '\u315F': '\u1171',
+ '\u3160': '\u1172',
+ '\u3161': '\u1173',
+ '\u3162': '\u1174',
+ '\u3163': '\u1175',
+ '\u3164': '\u1160',
+ '\u3165': '\u1114',
+ '\u3166': '\u1115',
+ '\u3167': '\u11C7',
+ '\u3168': '\u11C8',
+ '\u3169': '\u11CC',
+ '\u316A': '\u11CE',
+ '\u316B': '\u11D3',
+ '\u316C': '\u11D7',
+ '\u316D': '\u11D9',
+ '\u316E': '\u111C',
+ '\u316F': '\u11DD',
+ '\u3170': '\u11DF',
+ '\u3171': '\u111D',
+ '\u3172': '\u111E',
+ '\u3173': '\u1120',
+ '\u3174': '\u1122',
+ '\u3175': '\u1123',
+ '\u3176': '\u1127',
+ '\u3177': '\u1129',
+ '\u3178': '\u112B',
+ '\u3179': '\u112C',
+ '\u317A': '\u112D',
+ '\u317B': '\u112E',
+ '\u317C': '\u112F',
+ '\u317D': '\u1132',
+ '\u317E': '\u1136',
+ '\u317F': '\u1140',
+ '\u3180': '\u1147',
+ '\u3181': '\u114C',
+ '\u3182': '\u11F1',
+ '\u3183': '\u11F2',
+ '\u3184': '\u1157',
+ '\u3185': '\u1158',
+ '\u3186': '\u1159',
+ '\u3187': '\u1184',
+ '\u3188': '\u1185',
+ '\u3189': '\u1188',
+ '\u318A': '\u1191',
+ '\u318B': '\u1192',
+ '\u318C': '\u1194',
+ '\u318D': '\u119E',
+ '\u318E': '\u11A1',
+ '\u3200': '\u0028\u1100\u0029',
+ '\u3201': '\u0028\u1102\u0029',
+ '\u3202': '\u0028\u1103\u0029',
+ '\u3203': '\u0028\u1105\u0029',
+ '\u3204': '\u0028\u1106\u0029',
+ '\u3205': '\u0028\u1107\u0029',
+ '\u3206': '\u0028\u1109\u0029',
+ '\u3207': '\u0028\u110B\u0029',
+ '\u3208': '\u0028\u110C\u0029',
+ '\u3209': '\u0028\u110E\u0029',
+ '\u320A': '\u0028\u110F\u0029',
+ '\u320B': '\u0028\u1110\u0029',
+ '\u320C': '\u0028\u1111\u0029',
+ '\u320D': '\u0028\u1112\u0029',
+ '\u320E': '\u0028\u1100\u1161\u0029',
+ '\u320F': '\u0028\u1102\u1161\u0029',
+ '\u3210': '\u0028\u1103\u1161\u0029',
+ '\u3211': '\u0028\u1105\u1161\u0029',
+ '\u3212': '\u0028\u1106\u1161\u0029',
+ '\u3213': '\u0028\u1107\u1161\u0029',
+ '\u3214': '\u0028\u1109\u1161\u0029',
+ '\u3215': '\u0028\u110B\u1161\u0029',
+ '\u3216': '\u0028\u110C\u1161\u0029',
+ '\u3217': '\u0028\u110E\u1161\u0029',
+ '\u3218': '\u0028\u110F\u1161\u0029',
+ '\u3219': '\u0028\u1110\u1161\u0029',
+ '\u321A': '\u0028\u1111\u1161\u0029',
+ '\u321B': '\u0028\u1112\u1161\u0029',
+ '\u321C': '\u0028\u110C\u116E\u0029',
+ '\u321D': '\u0028\u110B\u1169\u110C\u1165\u11AB\u0029',
+ '\u321E': '\u0028\u110B\u1169\u1112\u116E\u0029',
+ '\u3220': '\u0028\u4E00\u0029',
+ '\u3221': '\u0028\u4E8C\u0029',
+ '\u3222': '\u0028\u4E09\u0029',
+ '\u3223': '\u0028\u56DB\u0029',
+ '\u3224': '\u0028\u4E94\u0029',
+ '\u3225': '\u0028\u516D\u0029',
+ '\u3226': '\u0028\u4E03\u0029',
+ '\u3227': '\u0028\u516B\u0029',
+ '\u3228': '\u0028\u4E5D\u0029',
+ '\u3229': '\u0028\u5341\u0029',
+ '\u322A': '\u0028\u6708\u0029',
+ '\u322B': '\u0028\u706B\u0029',
+ '\u322C': '\u0028\u6C34\u0029',
+ '\u322D': '\u0028\u6728\u0029',
+ '\u322E': '\u0028\u91D1\u0029',
+ '\u322F': '\u0028\u571F\u0029',
+ '\u3230': '\u0028\u65E5\u0029',
+ '\u3231': '\u0028\u682A\u0029',
+ '\u3232': '\u0028\u6709\u0029',
+ '\u3233': '\u0028\u793E\u0029',
+ '\u3234': '\u0028\u540D\u0029',
+ '\u3235': '\u0028\u7279\u0029',
+ '\u3236': '\u0028\u8CA1\u0029',
+ '\u3237': '\u0028\u795D\u0029',
+ '\u3238': '\u0028\u52B4\u0029',
+ '\u3239': '\u0028\u4EE3\u0029',
+ '\u323A': '\u0028\u547C\u0029',
+ '\u323B': '\u0028\u5B66\u0029',
+ '\u323C': '\u0028\u76E3\u0029',
+ '\u323D': '\u0028\u4F01\u0029',
+ '\u323E': '\u0028\u8CC7\u0029',
+ '\u323F': '\u0028\u5354\u0029',
+ '\u3240': '\u0028\u796D\u0029',
+ '\u3241': '\u0028\u4F11\u0029',
+ '\u3242': '\u0028\u81EA\u0029',
+ '\u3243': '\u0028\u81F3\u0029',
+ '\u32C0': '\u0031\u6708',
+ '\u32C1': '\u0032\u6708',
+ '\u32C2': '\u0033\u6708',
+ '\u32C3': '\u0034\u6708',
+ '\u32C4': '\u0035\u6708',
+ '\u32C5': '\u0036\u6708',
+ '\u32C6': '\u0037\u6708',
+ '\u32C7': '\u0038\u6708',
+ '\u32C8': '\u0039\u6708',
+ '\u32C9': '\u0031\u0030\u6708',
+ '\u32CA': '\u0031\u0031\u6708',
+ '\u32CB': '\u0031\u0032\u6708',
+ '\u3358': '\u0030\u70B9',
+ '\u3359': '\u0031\u70B9',
+ '\u335A': '\u0032\u70B9',
+ '\u335B': '\u0033\u70B9',
+ '\u335C': '\u0034\u70B9',
+ '\u335D': '\u0035\u70B9',
+ '\u335E': '\u0036\u70B9',
+ '\u335F': '\u0037\u70B9',
+ '\u3360': '\u0038\u70B9',
+ '\u3361': '\u0039\u70B9',
+ '\u3362': '\u0031\u0030\u70B9',
+ '\u3363': '\u0031\u0031\u70B9',
+ '\u3364': '\u0031\u0032\u70B9',
+ '\u3365': '\u0031\u0033\u70B9',
+ '\u3366': '\u0031\u0034\u70B9',
+ '\u3367': '\u0031\u0035\u70B9',
+ '\u3368': '\u0031\u0036\u70B9',
+ '\u3369': '\u0031\u0037\u70B9',
+ '\u336A': '\u0031\u0038\u70B9',
+ '\u336B': '\u0031\u0039\u70B9',
+ '\u336C': '\u0032\u0030\u70B9',
+ '\u336D': '\u0032\u0031\u70B9',
+ '\u336E': '\u0032\u0032\u70B9',
+ '\u336F': '\u0032\u0033\u70B9',
+ '\u3370': '\u0032\u0034\u70B9',
+ '\u33E0': '\u0031\u65E5',
+ '\u33E1': '\u0032\u65E5',
+ '\u33E2': '\u0033\u65E5',
+ '\u33E3': '\u0034\u65E5',
+ '\u33E4': '\u0035\u65E5',
+ '\u33E5': '\u0036\u65E5',
+ '\u33E6': '\u0037\u65E5',
+ '\u33E7': '\u0038\u65E5',
+ '\u33E8': '\u0039\u65E5',
+ '\u33E9': '\u0031\u0030\u65E5',
+ '\u33EA': '\u0031\u0031\u65E5',
+ '\u33EB': '\u0031\u0032\u65E5',
+ '\u33EC': '\u0031\u0033\u65E5',
+ '\u33ED': '\u0031\u0034\u65E5',
+ '\u33EE': '\u0031\u0035\u65E5',
+ '\u33EF': '\u0031\u0036\u65E5',
+ '\u33F0': '\u0031\u0037\u65E5',
+ '\u33F1': '\u0031\u0038\u65E5',
+ '\u33F2': '\u0031\u0039\u65E5',
+ '\u33F3': '\u0032\u0030\u65E5',
+ '\u33F4': '\u0032\u0031\u65E5',
+ '\u33F5': '\u0032\u0032\u65E5',
+ '\u33F6': '\u0032\u0033\u65E5',
+ '\u33F7': '\u0032\u0034\u65E5',
+ '\u33F8': '\u0032\u0035\u65E5',
+ '\u33F9': '\u0032\u0036\u65E5',
+ '\u33FA': '\u0032\u0037\u65E5',
+ '\u33FB': '\u0032\u0038\u65E5',
+ '\u33FC': '\u0032\u0039\u65E5',
+ '\u33FD': '\u0033\u0030\u65E5',
+ '\u33FE': '\u0033\u0031\u65E5',
+ '\uFB00': '\u0066\u0066',
+ '\uFB01': '\u0066\u0069',
+ '\uFB02': '\u0066\u006C',
+ '\uFB03': '\u0066\u0066\u0069',
+ '\uFB04': '\u0066\u0066\u006C',
+ '\uFB05': '\u017F\u0074',
+ '\uFB06': '\u0073\u0074',
+ '\uFB13': '\u0574\u0576',
+ '\uFB14': '\u0574\u0565',
+ '\uFB15': '\u0574\u056B',
+ '\uFB16': '\u057E\u0576',
+ '\uFB17': '\u0574\u056D',
+ '\uFB4F': '\u05D0\u05DC',
+ '\uFE49': '\u203E',
+ '\uFE4A': '\u203E',
+ '\uFE4B': '\u203E',
+ '\uFE4C': '\u203E',
+ '\uFE4D': '\u005F',
+ '\uFE4E': '\u005F',
+ '\uFE4F': '\u005F'
+};
+
+function fontCharsToUnicode(charCodes, fontProperties) {
+ var toUnicode = fontProperties.toUnicode;
+ var composite = fontProperties.composite;
+ var encoding, differences, cidToUnicode;
+ var result = '';
+ if (composite) {
+ cidToUnicode = fontProperties.cidToUnicode;
+ for (var i = 0, ii = charCodes.length; i < ii; i += 2) {
+ var charCode = (charCodes.charCodeAt(i) << 8) |
+ charCodes.charCodeAt(i + 1);
+ if (toUnicode && charCode in toUnicode) {
+ var unicode = toUnicode[charCode];
+ result += typeof unicode !== 'number' ? unicode :
+ String.fromCharCode(unicode);
+ continue;
+ }
+ result += String.fromCharCode(!cidToUnicode ? charCode :
+ cidToUnicode[charCode] || charCode);
+ }
+ } else {
+ differences = fontProperties.differences;
+ encoding = fontProperties.baseEncoding;
+ for (var i = 0, ii = charCodes.length; i < ii; i++) {
+ var charCode = charCodes.charCodeAt(i);
+ var unicode;
+ if (toUnicode && charCode in toUnicode) {
+ var unicode = toUnicode[charCode];
+ result += typeof unicode !== 'number' ? unicode :
+ String.fromCharCode(unicode);
+ continue;
+ }
+
+ var glyphName = charCode in differences ? differences[charCode] :
+ encoding[charCode];
+ if (glyphName in GlyphsUnicode) {
+ result += String.fromCharCode(GlyphsUnicode[glyphName]);
+ continue;
+ }
+ result += String.fromCharCode(charCode);
+ }
+ }
+ // normalizing the unicode characters
+ for (var i = 0, ii = result.length; i < ii; i++) {
+ if (!(result[i] in NormalizedUnicodes))
+ continue;
+ result = result.substring(0, i) + NormalizedUnicodes[result[i]] +
+ result.substring(i + 1);
+ ii = result.length;
+ }
+ return result;
+}
+
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
@@ -14148,7 +15518,6 @@ var Font = (function FontClosure() {
this.name = name;
this.coded = properties.coded;
this.charProcOperatorList = properties.charProcOperatorList;
- this.resources = properties.resources;
this.sizes = [];
var names = name.split('+');
@@ -14172,6 +15541,7 @@ var Font = (function FontClosure() {
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite;
+ this.wideChars = properties.wideChars;
this.hasEncoding = properties.hasEncoding;
this.fontMatrix = properties.fontMatrix;
@@ -15109,6 +16479,16 @@ var Font = (function FontClosure() {
properties.glyphNames = glyphNames;
}
+ function isOS2Valid(os2Table) {
+ var data = os2Table.data;
+ // usWinAscent == 0 makes font unreadable by windows
+ var usWinAscent = (data[74] << 8) | data[75];
+ if (usWinAscent == 0)
+ return false;
+
+ return true;
+ }
+
// Check that required tables are present
var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
'hmtx', 'maxp', 'name', 'post'];
@@ -15116,7 +16496,7 @@ var Font = (function FontClosure() {
var header = readOpenTypeHeader(font);
var numTables = header.numTables;
- var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
+ var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf, os2;
var tables = [];
for (var i = 0; i < numTables; i++) {
var table = readTableEntry(font);
@@ -15134,6 +16514,8 @@ var Font = (function FontClosure() {
hmtx = table;
else if (table.tag == 'head')
head = table;
+ else if (table.tag == 'OS/2')
+ os2 = table;
requiredTables.splice(index, 1);
} else {
@@ -15149,7 +16531,7 @@ var Font = (function FontClosure() {
tables.push(table);
}
- var numTables = header.numTables + requiredTables.length;
+ var numTables = tables.length + requiredTables.length;
// header and new offsets. Table entry information is appended to the
// end of file. The virtualOffset represents where to put the actual
@@ -15163,21 +16545,10 @@ var Font = (function FontClosure() {
// of missing tables
createOpenTypeHeader(header.version, ttf, numTables);
- if (requiredTables.indexOf('OS/2') != -1) {
- // extract some more font properties from the OpenType head and
- // hhea tables; yMin and descent value are always negative
- var override = {
- unitsPerEm: int16([head.data[18], head.data[19]]),
- yMax: int16([head.data[42], head.data[43]]),
- yMin: int16([head.data[38], head.data[39]]) - 0x10000,
- ascent: int16([hhea.data[4], hhea.data[5]]),
- descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
- };
-
- tables.push({
- tag: 'OS/2',
- data: stringToArray(createOS2Table(properties, null, override))
- });
+ // Invalid OS/2 can break the font for the Windows
+ if (os2 && !isOS2Valid(os2)) {
+ tables.splice(tables.indexOf(os2), 1);
+ os2 = null;
}
// Ensure the [h/v]mtx tables contains the advance width and
@@ -15357,9 +16728,9 @@ var Font = (function FontClosure() {
this.isSymbolicFont = false;
}
- // heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding
- // does not set properly
- if (glyphsRemoved > 2) {
+ // heuristics: if removed more than 10 glyphs encoding WinAnsiEncoding
+ // does not set properly (broken PDFs have about 100 removed glyphs)
+ if (glyphsRemoved > 10) {
warn('Switching TrueType encoding to MacRomanEncoding for ' +
this.name + ' font');
encoding = Encodings.MacRomanEncoding;
@@ -15458,6 +16829,23 @@ var Font = (function FontClosure() {
}
this.unicodeIsEnabled = unicodeIsEnabled;
+ if (!os2) {
+ // extract some more font properties from the OpenType head and
+ // hhea tables; yMin and descent value are always negative
+ var override = {
+ unitsPerEm: int16([head.data[18], head.data[19]]),
+ yMax: int16([head.data[42], head.data[43]]),
+ yMin: int16([head.data[38], head.data[39]]) - 0x10000,
+ ascent: int16([hhea.data[4], hhea.data[5]]),
+ descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
+ };
+
+ tables.push({
+ tag: 'OS/2',
+ data: stringToArray(createOS2Table(properties, glyphs, override))
+ });
+ }
+
// Rewrite the 'post' table if needed
if (requiredTables.indexOf('post') != -1) {
tables.push({
@@ -15842,6 +17230,7 @@ var Font = (function FontClosure() {
}
// MacRoman encoding address by re-encoding the cmap table
+
fontCharCode = glyphName in this.glyphNameMap ?
this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
break;
@@ -15885,7 +17274,7 @@ var Font = (function FontClosure() {
glyphs = [];
- if (this.composite) {
+ if (this.wideChars) {
// composite fonts have multi-byte strings convert the string from
// single-byte to multi-byte
// XXX assuming CIDFonts are two-byte - later need to extract the
@@ -16841,7 +18230,7 @@ var CFFFont = (function CFFFontClosure() {
this.properties = properties;
var parser = new CFFParser(file, properties);
- var cff = parser.parse();
+ var cff = parser.parse(true);
var compiler = new CFFCompiler(cff);
this.readExtra(cff);
try {
@@ -16932,7 +18321,7 @@ var CFFParser = (function CFFParserClosure() {
this.properties = properties;
}
CFFParser.prototype = {
- parse: function CFFParser_parse() {
+ parse: function CFFParser_parse(normalizeCIDData) {
var properties = this.properties;
var cff = new CFF();
this.cff = cff;
@@ -16987,6 +18376,21 @@ var CFFParser = (function CFFParserClosure() {
cff.charset = charset;
cff.encoding = encoding;
+ if (!cff.isCIDFont || !normalizeCIDData)
+ return cff;
+
+ // DirectWrite does not like CID fonts data. Trying to convert/flatten
+ // the font data and remove CID properties.
+ if (cff.fdArray.length !== 1)
+ error('Unable to normalize CID font in CFF data');
+
+ var fontDict = cff.fdArray[0];
+ fontDict.setByKey(17, topDict.getByName('CharStrings'));
+ cff.topDict = fontDict;
+ cff.isCIDFont = false;
+ delete cff.fdArray;
+ delete cff.fdSelect;
+
return cff;
},
parseHeader: function CFFParser_parseHeader() {
@@ -16997,7 +18401,7 @@ var CFFParser = (function CFFParserClosure() {
++offset;
if (offset != 0) {
- warn('cff data is shifted');
+ info('cff data is shifted');
bytes = bytes.subarray(offset);
this.bytes = bytes;
}
@@ -17585,9 +18989,9 @@ var CFFPrivateDict = (function CFFPrivateDictClosure() {
[[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]
+ [21, 'nominalWidthX', 'num', 0],
+ [19, 'Subrs', 'offset', null]
];
var tables = null;
function CFFPrivateDict(strings) {
@@ -18045,6 +19449,7 @@ var CFFCompiler = (function CFFCompilerClosure() {
return CFFCompiler;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -22257,6 +23662,7 @@ var GlyphsUnicode = {
'.notdef': 0x0000
};
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -22274,7 +23680,7 @@ var PDFImage = (function PDFImageClosure() {
var colorSpace = dict.get('ColorSpace', 'CS');
colorSpace = ColorSpace.parse(colorSpace, xref, res);
var numComps = colorSpace.numComps;
- handler.send('jpeg_decode', [image.getIR(), numComps], function(message) {
+ handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
var data = message.data;
var stream = new Stream(data, 0, data.length, image.dict);
promise.resolve(stream);
@@ -22632,6 +24038,7 @@ function loadJpegStream(id, imageData, objs) {
img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
}
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -25579,6 +26986,7 @@ var Metrics = {
}
};
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -25830,9 +27238,12 @@ var Parser = (function ParserClosure() {
if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params);
}
- if (name == 'RunLengthDecode') {
+ if (name == 'RunLengthDecode' || name == 'RL') {
return new RunLengthStream(stream);
}
+ if (name == 'JBIG2Decode') {
+ error('JBIG2 image format is not currently supprted.');
+ }
warn('filter "' + name + '" not supported yet');
return stream;
}
@@ -25842,8 +27253,16 @@ var Parser = (function ParserClosure() {
})();
var Lexer = (function LexerClosure() {
- function Lexer(stream) {
+ function Lexer(stream, knownCommands) {
this.stream = stream;
+ // The PDFs might have "glued" commands with other commands, operands or
+ // literals, e.g. "q1". The knownCommands is a dictionary of the valid
+ // commands and their prefixes. The prefixes are built the following way:
+ // if there a command that is a prefix of the other valid command or
+ // literal (e.g. 'f' and 'false') the following prefixes must be included,
+ // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
+ // other commands or literals as a prefix. The knowCommands is optional.
+ this.knownCommands = knownCommands;
}
Lexer.isSpace = function Lexer_isSpace(ch) {
@@ -26107,12 +27526,18 @@ var Lexer = (function LexerClosure() {
// command
var str = ch;
+ var knownCommands = this.knownCommands;
+ var knownCommandFound = knownCommands && (str in knownCommands);
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
+ // stop if known command is found and next character does not make
+ // the str a command
+ if (knownCommandFound && !((str + ch) in knownCommands))
+ break;
stream.skip();
if (str.length == 128)
error('Command token too long: ' + str.length);
-
str += ch;
+ knownCommandFound = knownCommands && (str in knownCommands);
}
if (str == 'true')
return true;
@@ -26218,6 +27643,7 @@ var Linearization = (function LinearizationClosure() {
return Linearization;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -26330,7 +27756,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
var r1 = raw[6];
return {
type: 'Pattern',
- getPattern: function(ctx) {
+ getPattern: function RadialAxial_getPattern(ctx) {
var curMatrix = ctx.mozCurrentTransform;
if (curMatrix) {
var userMatrix = ctx.mozCurrentTransformInverse;
@@ -26521,6 +27947,7 @@ var TilingPattern = (function TilingPatternClosure() {
return TilingPattern;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -28210,7 +29637,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
if (a1 > codingLine[codingPos]) {
if (a1 > this.columns) {
- warn('row is wrong length');
+ info('row is wrong length');
this.err = true;
a1 = this.columns;
}
@@ -28230,7 +29657,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
if (a1 > codingLine[codingPos]) {
if (a1 > this.columns) {
- warn('row is wrong length');
+ info('row is wrong length');
this.err = true;
a1 = this.columns;
}
@@ -28240,7 +29667,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
codingLine[codingPos] = a1;
} else if (a1 < codingLine[codingPos]) {
if (a1 < 0) {
- warn('invalid code');
+ info('invalid code');
this.err = true;
a1 = 0;
}
@@ -28402,7 +29829,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
this.eof = true;
break;
default:
- warn('bad 2d code');
+ info('bad 2d code');
this.addPixels(columns, 0);
this.err = true;
}
@@ -28465,7 +29892,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
for (var i = 0; i < 4; ++i) {
code1 = this.lookBits(12);
if (code1 != 1)
- warn('bad rtc code: ' + code1);
+ info('bad rtc code: ' + code1);
this.eatBits(12);
if (this.encoding > 0) {
this.lookBits(1);
@@ -28588,7 +30015,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
if (result[0] && result[2])
return result[1];
}
- warn('Bad two dim code');
+ info('Bad two dim code');
return EOF;
};
@@ -28621,7 +30048,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
if (result[0])
return result[1];
}
- warn('bad white code');
+ info('bad white code');
this.eatBits(1);
return 1;
};
@@ -28658,7 +30085,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
if (result[0])
return result[1];
}
- warn('bad black code');
+ info('bad black code');
this.eatBits(1);
return 1;
};
@@ -28815,6 +30242,7 @@ var LZWStream = (function LZWStreamClosure() {
return LZWStream;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -28828,10 +30256,13 @@ function MessageHandler(name, comObj) {
var ah = this.actionHandler = {};
ah['console_log'] = [function ahConsoleLog(data) {
- console.log.apply(console, data);
+ console.log.apply(console, data);
}];
ah['console_error'] = [function ahConsoleError(data) {
- console.error.apply(console, data);
+ console.error.apply(console, data);
+ }];
+ ah['_warn'] = [function ah_Warn(data) {
+ warn(data);
}];
comObj.onmessage = function messageHandlerComObjOnMessage(event) {
@@ -28902,15 +30333,68 @@ var WorkerMessageHandler = {
handler.send('test', data instanceof Uint8Array);
});
- handler.on('doc', function wphSetupDoc(data) {
+ handler.on('GetDocRequest', function wphSetupDoc(data) {
// Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf.
- pdfModel = new PDFDocModel(new Stream(data));
+ var pdfData = data.data;
+ var pdfPassword = data.params.password;
+ try {
+ pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword);
+ } catch (e) {
+ if (e instanceof PasswordException) {
+ if (e.code === 'needpassword') {
+ handler.send('NeedPassword', {
+ exception: e
+ });
+ } else if (e.code === 'incorrectpassword') {
+ handler.send('IncorrectPassword', {
+ exception: e
+ });
+ }
+
+ return;
+ } else {
+ throw e;
+ }
+ }
+ var doc = {
+ numPages: pdfModel.numPages,
+ fingerprint: pdfModel.getFingerprint(),
+ destinations: pdfModel.catalog.destinations,
+ outline: pdfModel.catalog.documentOutline,
+ info: pdfModel.getDocumentInfo(),
+ metadata: pdfModel.catalog.metadata,
+ encrypted: !!pdfModel.xref.encrypt
+ };
+ handler.send('GetDoc', {pdfInfo: doc});
});
- handler.on('page_request', function wphSetupPageRequest(pageNum) {
- pageNum = parseInt(pageNum);
+ handler.on('GetPageRequest', function wphSetupGetPage(data) {
+ var pageNumber = data.pageIndex + 1;
+ var pdfPage = pdfModel.getPage(pageNumber);
+ var page = {
+ pageIndex: data.pageIndex,
+ rotate: pdfPage.rotate,
+ ref: pdfPage.ref,
+ view: pdfPage.view
+ };
+ handler.send('GetPage', {pageInfo: page});
+ });
+
+ handler.on('GetData', function wphSetupGetData(data, promise) {
+ promise.resolve(pdfModel.stream.bytes);
+ });
+
+ handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
+ var pdfPage = pdfModel.getPage(data.pageIndex + 1);
+ handler.send('GetAnnotations', {
+ pageIndex: data.pageIndex,
+ annotations: pdfPage.getAnnotations()
+ });
+ });
+ handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
+ var pageNum = data.pageIndex + 1;
// The following code does quite the same as
// Page.prototype.startRendering, but stops at one point and sends the
@@ -28947,7 +30431,7 @@ var WorkerMessageHandler = {
};
}
- handler.send('page_error', {
+ handler.send('PageError', {
pageNum: pageNum,
error: e
});
@@ -28965,13 +30449,30 @@ var WorkerMessageHandler = {
fonts[dep] = true;
}
}
-
- handler.send('page', {
- pageNum: pageNum,
+ handler.send('RenderPage', {
+ pageIndex: data.pageIndex,
operatorList: operatorList,
depFonts: Object.keys(fonts)
});
}, this);
+
+ handler.on('GetTextContent', function wphExtractText(data, promise) {
+ var pageNum = data.pageIndex + 1;
+ var start = Date.now();
+
+ var textContent = '';
+ try {
+ var page = pdfModel.getPage(pageNum);
+ textContent = page.extractTextContent();
+ promise.resolve(textContent);
+ } catch (e) {
+ // Skip errored pages
+ promise.reject(e);
+ }
+
+ console.log('text indexing: page=%d - time=%dms',
+ pageNum, Date.now() - start);
+ });
}
};
@@ -29012,10 +30513,22 @@ var workerConsole = {
if (typeof window === 'undefined') {
globalScope.console = workerConsole;
+ // Add a logger so we can pass warnings on to the main thread, errors will
+ // throw an exception which will be forwarded on automatically.
+ PDFJS.LogManager.addLogger({
+ warn: function(msg) {
+ postMessage({
+ action: '_warn',
+ data: msg
+ });
+ }
+ });
+
var handler = new MessageHandler('worker_processor', this);
WorkerMessageHandler.setup(handler);
}
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -29655,7 +31168,10 @@ var JpegImage = (function jpegImage() {
tableData[z] = data[offset++];
}
} else if ((quantizationTableSpec >> 4) === 1) { //16 bit
- tableData[j] = readUint16();
+ for (j = 0; j < 64; j++) {
+ var z = dctZigZag[j];
+ tableData[z] = readUint16();
+ }
} else
throw "DQT: invalid table spec";
quantizationTables[quantizationTableSpec & 15] = tableData;
@@ -29670,7 +31186,8 @@ var JpegImage = (function jpegImage() {
frame.precision = data[offset++];
frame.scanLines = readUint16();
frame.samplesPerLine = readUint16();
- frame.components = [];
+ frame.components = {};
+ frame.componentsOrder = [];
var componentsCount = data[offset++], componentId;
var maxH = 0, maxV = 0;
for (i = 0; i < componentsCount; i++) {
@@ -29678,6 +31195,7 @@ var JpegImage = (function jpegImage() {
var h = data[offset + 1] >> 4;
var v = data[offset + 1] & 15;
var qId = data[offset + 2];
+ frame.componentsOrder.push(componentId);
frame.components[componentId] = {
h: h,
v: v,
@@ -29746,14 +31264,13 @@ var JpegImage = (function jpegImage() {
this.jfif = jfif;
this.adobe = adobe;
this.components = [];
- for (var id in frame.components) {
- if (frame.components.hasOwnProperty(id)) {
- this.components.push({
- lines: buildComponentData(frame, frame.components[id]),
- scaleX: frame.components[id].h / frame.maxH,
- scaleY: frame.components[id].v / frame.maxV
- });
- }
+ for (var i = 0; i < frame.componentsOrder.length; i++) {
+ var component = frame.components[frame.componentsOrder[i]];
+ this.components.push({
+ lines: buildComponentData(frame, component),
+ scaleX: component.h / frame.maxH,
+ scaleY: component.v / frame.maxV
+ });
}
},
getData: function getData(width, height) {
@@ -29926,7 +31443,8 @@ var JpegImage = (function jpegImage() {
};
return constructor;
-})();/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+})();
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
@@ -31788,6 +33306,7 @@ var JpxImage = (function JpxImageClosure() {
return JpxImage;
})();
+
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
@@ -32220,14 +33739,35 @@ var bidi = PDFJS.bidi = (function bidiClosure() {
return bidi;
})();
+
/* -*- 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 fixMetadata(meta) {
+ return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) {
+ var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g,
+ function(code, d1, d2, d3) {
+ return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+ });
+ var chars = '';
+ for (var i = 0; i < bytes.length; i += 2) {
+ var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
+ chars += code >= 32 && code < 127 && code != 60 && code != 62 &&
+ code != 38 && false ? String.fromCharCode(code) :
+ '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+ }
+ return '>' + chars;
+ });
+ }
+
function Metadata(meta) {
if (typeof meta === 'string') {
+ // Ghostscript produces invalid metadata
+ meta = fixMetadata(meta);
+
var parser = new DOMParser();
meta = parser.parseFromString(meta, 'application/xml');
} else if (!(meta instanceof Document)) {
diff --git a/apps/files_pdfviewer/js/pdfjs/compatibility.js b/apps/files_pdfviewer/js/pdfjs/compatibility.js
new file mode 100644
index 00000000000..528841bb614
--- /dev/null
+++ b/apps/files_pdfviewer/js/pdfjs/compatibility.js
@@ -0,0 +1,340 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// Checking if the typed arrays are supported
+(function checkTypedArrayCompatibility() {
+ if (typeof Uint8Array !== 'undefined') {
+ // some mobile version might not support Float64Array
+ if (typeof Float64Array === 'undefined')
+ window.Float64Array = Float32Array;
+
+ return;
+ }
+
+ function subarray(start, end) {
+ return new TypedArray(this.slice(start, end));
+ }
+
+ function setArrayOffset(array, offset) {
+ if (arguments.length < 2)
+ offset = 0;
+ for (var i = 0, n = array.length; i < n; ++i, ++offset)
+ this[offset] = array[i] & 0xFF;
+ }
+
+ function TypedArray(arg1) {
+ var result;
+ if (typeof arg1 === 'number') {
+ result = [];
+ for (var i = 0; i < arg1; ++i)
+ result[i] = 0;
+ } else
+ result = arg1.slice(0);
+
+ result.subarray = subarray;
+ result.buffer = result;
+ result.byteLength = result.length;
+ result.set = setArrayOffset;
+
+ if (typeof arg1 === 'object' && arg1.buffer)
+ result.buffer = arg1.buffer;
+
+ return result;
+ }
+
+ window.Uint8Array = TypedArray;
+
+ // we don't need support for set, byteLength for 32-bit array
+ // so we can use the TypedArray as well
+ window.Uint32Array = TypedArray;
+ window.Int32Array = TypedArray;
+ window.Uint16Array = TypedArray;
+ window.Float32Array = TypedArray;
+ window.Float64Array = TypedArray;
+})();
+
+// Object.create() ?
+(function checkObjectCreateCompatibility() {
+ if (typeof Object.create !== 'undefined')
+ return;
+
+ Object.create = function objectCreate(proto) {
+ var constructor = function objectCreateConstructor() {};
+ constructor.prototype = proto;
+ return new constructor();
+ };
+})();
+
+// Object.defineProperty() ?
+(function checkObjectDefinePropertyCompatibility() {
+ if (typeof Object.defineProperty !== 'undefined')
+ return;
+
+ Object.defineProperty = function objectDefineProperty(obj, name, def) {
+ delete obj[name];
+ if ('get' in def)
+ obj.__defineGetter__(name, def['get']);
+ if ('set' in def)
+ obj.__defineSetter__(name, def['set']);
+ if ('value' in def) {
+ obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
+ this.__defineGetter__(name, function objectDefinePropertyGetter() {
+ return value;
+ });
+ return value;
+ });
+ obj[name] = def.value;
+ }
+ };
+})();
+
+// Object.keys() ?
+(function checkObjectKeysCompatibility() {
+ if (typeof Object.keys !== 'undefined')
+ return;
+
+ Object.keys = function objectKeys(obj) {
+ var result = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i))
+ result.push(i);
+ }
+ return result;
+ };
+})();
+
+// No XMLHttpRequest.response ?
+(function checkXMLHttpRequestResponseCompatibility() {
+ var xhrPrototype = XMLHttpRequest.prototype;
+ if ('response' in xhrPrototype ||
+ 'mozResponseArrayBuffer' in xhrPrototype ||
+ 'mozResponse' in xhrPrototype ||
+ 'responseArrayBuffer' in xhrPrototype)
+ return;
+ // IE ?
+ if (typeof VBArray !== 'undefined') {
+ Object.defineProperty(xhrPrototype, 'response', {
+ get: function xmlHttpRequestResponseGet() {
+ return new Uint8Array(new VBArray(this.responseBody).toArray());
+ }
+ });
+ Object.defineProperty(xhrPrototype, 'overrideMimeType', {
+ value: function xmlHttpRequestOverrideMimeType(mimeType) {}
+ });
+ return;
+ }
+
+ // other browsers
+ function responseTypeSetter() {
+ // will be only called to set "arraybuffer"
+ this.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+ if (typeof xhrPrototype.overrideMimeType === 'function') {
+ Object.defineProperty(xhrPrototype, 'responseType',
+ { set: responseTypeSetter });
+ }
+ function responseGetter() {
+ var text = this.responseText;
+ var i, n = text.length;
+ var result = new Uint8Array(n);
+ for (i = 0; i < n; ++i)
+ result[i] = text.charCodeAt(i) & 0xFF;
+ return result;
+ }
+ Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
+})();
+
+// window.btoa (base64 encode function) ?
+(function checkWindowBtoaCompatibility() {
+ if ('btoa' in window)
+ return;
+
+ var digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+ window.btoa = function windowBtoa(chars) {
+ var buffer = '';
+ var i, n;
+ for (i = 0, n = chars.length; i < n; i += 3) {
+ var b1 = chars.charCodeAt(i) & 0xFF;
+ var b2 = chars.charCodeAt(i + 1) & 0xFF;
+ var b3 = chars.charCodeAt(i + 2) & 0xFF;
+ var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+ var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+ var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
+ buffer += (digits.charAt(d1) + digits.charAt(d2) +
+ digits.charAt(d3) + digits.charAt(d4));
+ }
+ return buffer;
+ };
+})();
+
+// Function.prototype.bind ?
+(function checkFunctionPrototypeBindCompatibility() {
+ if (typeof Function.prototype.bind !== 'undefined')
+ return;
+
+ Function.prototype.bind = function functionPrototypeBind(obj) {
+ var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
+ var bound = function functionPrototypeBindBound() {
+ var args = Array.prototype.concat.apply(headArgs, arguments);
+ return fn.apply(obj, args);
+ };
+ return bound;
+ };
+})();
+
+// IE9 text/html data URI
+(function checkDocumentDocumentModeCompatibility() {
+ if (!('documentMode' in document) || document.documentMode !== 9)
+ return;
+ // overriding the src property
+ var originalSrcDescriptor = Object.getOwnPropertyDescriptor(
+ HTMLIFrameElement.prototype, 'src');
+ Object.defineProperty(HTMLIFrameElement.prototype, 'src', {
+ get: function htmlIFrameElementPrototypeSrcGet() { return this.$src; },
+ set: function htmlIFrameElementPrototypeSrcSet(src) {
+ this.$src = src;
+ if (src.substr(0, 14) != 'data:text/html') {
+ originalSrcDescriptor.set.call(this, src);
+ return;
+ }
+ // for text/html, using blank document and then
+ // document's open, write, and close operations
+ originalSrcDescriptor.set.call(this, 'about:blank');
+ setTimeout((function htmlIFrameElementPrototypeSrcOpenWriteClose() {
+ var doc = this.contentDocument;
+ doc.open('text/html');
+ doc.write(src.substr(src.indexOf(',') + 1));
+ doc.close();
+ }).bind(this), 0);
+ },
+ enumerable: true
+ });
+})();
+
+// HTMLElement dataset property
+(function checkDatasetProperty() {
+ var div = document.createElement('div');
+ if ('dataset' in div)
+ return; // dataset property exists
+
+ Object.defineProperty(HTMLElement.prototype, 'dataset', {
+ get: function() {
+ if (this._dataset)
+ return this._dataset;
+
+ var dataset = {};
+ for (var j = 0, jj = this.attributes.length; j < jj; j++) {
+ var attribute = this.attributes[j];
+ if (attribute.name.substring(0, 5) != 'data-')
+ continue;
+ var key = attribute.name.substring(5).replace(/\-([a-z])/g,
+ function(all, ch) { return ch.toUpperCase(); });
+ dataset[key] = attribute.value;
+ }
+
+ Object.defineProperty(this, '_dataset', {
+ value: dataset,
+ writable: false,
+ enumerable: false
+ });
+ return dataset;
+ },
+ enumerable: true
+ });
+})();
+
+// HTMLElement classList property
+(function checkClassListProperty() {
+ var div = document.createElement('div');
+ if ('classList' in div)
+ return; // classList property exists
+
+ function changeList(element, itemName, add, remove) {
+ var s = element.className || '';
+ var list = s.split(/\s+/g);
+ if (list[0] == '') list.shift();
+ var index = list.indexOf(itemName);
+ if (index < 0 && add)
+ list.push(itemName);
+ if (index >= 0 && remove)
+ list.splice(index, 1);
+ element.className = list.join(' ');
+ }
+
+ var classListPrototype = {
+ add: function(name) {
+ changeList(this.element, name, true, false);
+ },
+ remove: function(name) {
+ changeList(this.element, name, false, true);
+ },
+ toggle: function(name) {
+ changeList(this.element, name, true, true);
+ }
+ };
+
+ Object.defineProperty(HTMLElement.prototype, 'classList', {
+ get: function() {
+ if (this._classList)
+ return this._classList;
+
+ var classList = Object.create(classListPrototype, {
+ element: {
+ value: this,
+ writable: false,
+ enumerable: true
+ }
+ });
+ Object.defineProperty(this, '_classList', {
+ value: classList,
+ writable: false,
+ enumerable: false
+ });
+ return classList;
+ },
+ enumerable: true
+ });
+})();
+
+// Check console compatability
+(function checkConsoleCompatibility() {
+ if (typeof console == 'undefined') {
+ console = {log: function() {}};
+ }
+})();
+
+// Check onclick compatibility in Opera
+(function checkOnClickCompatibility() {
+ // workaround for reported Opera bug DSK-354448:
+ // onclick fires on disabled buttons with opaque content
+ function ignoreIfTargetDisabled(event) {
+ if (isDisabled(event.target)) {
+ event.stopPropagation();
+ }
+ }
+ function isDisabled(node) {
+ return node.disabled || (node.parentNode && isDisabled(node.parentNode));
+ }
+ if (navigator.userAgent.indexOf('Opera') != -1) {
+ // use browser detection since we cannot feature-check this bug
+ document.addEventListener('click', ignoreIfTargetDisabled, true);
+ }
+})();
+
+// Checks if navigator.language is supported
+(function checkNavigatorLanguage() {
+ if ('language' in navigator)
+ return;
+ Object.defineProperty(navigator, 'language', {
+ get: function navigatorLanguage() {
+ var language = navigator.userLanguage || 'en-US';
+ return language.substring(0, 2).toLowerCase() +
+ language.substring(2).toUpperCase();
+ },
+ enumerable: true
+ });
+})();
diff --git a/apps/files_pdfviewer/js/pdfjs/update.sh b/apps/files_pdfviewer/js/pdfjs/update.sh
index 599f6338602..df100e780ba 100755
--- a/apps/files_pdfviewer/js/pdfjs/update.sh
+++ b/apps/files_pdfviewer/js/pdfjs/update.sh
@@ -1,3 +1,6 @@
cd build
rm pdf.js
wget http://mozilla.github.com/pdf.js/build/pdf.js
+wget http://mozilla.github.com/pdf.js/web/compatibility.js
+wget http://mozilla.github.com/pdf.js/web/viewer.js
+
diff --git a/apps/files_pdfviewer/js/pdfjs/viewer.js b/apps/files_pdfviewer/js/pdfjs/viewer.js
new file mode 100644
index 00000000000..ff4f2452b60
--- /dev/null
+++ b/apps/files_pdfviewer/js/pdfjs/viewer.js
@@ -0,0 +1,1936 @@
+/* -*- 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 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;
+var kMinScale = 0.25;
+var kMaxScale = 4.0;
+var kImageDirectory = './images/';
+var kSettingsMemory = 20;
+
+var mozL10n = document.mozL10n || document.webL10n;
+
+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) {
+ var i = data.indexOf(view);
+ if (i >= 0)
+ data.splice(i);
+ data.push(view);
+ if (data.length > size)
+ data.shift().destroy();
+ };
+};
+
+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;
+
+ if (this._percent > 95)
+ this.div.classList.add('full');
+
+ 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 = [];
+ }
+
+ RenderingQueue.prototype = {
+ enqueueDraw: function RenderingQueueEnqueueDraw(item) {
+ if (!item.drawingRequired())
+ return; // as no redraw required, no need for queueing.
+
+ this.items.push(item);
+ if (this.items.length > 1)
+ return; // not first item
+
+ item.draw(this.continueExecution.bind(this));
+ },
+ continueExecution: function RenderingQueueContinueExecution() {
+ var item = this.items.shift();
+
+ if (this.items.length == 0)
+ return; // queue is empty
+
+ item = this.items[0];
+ item.draw(this.continueExecution.bind(this));
+ }
+ };
+
+ return RenderingQueue;
+})();
+
+var FirefoxCom = (function FirefoxComClosure() {
+ return {
+ /**
+ * Creates an event that the extension is listening for and will
+ * synchronously respond to.
+ * NOTE: It is reccomended to use request() instead since one day we may not
+ * be able to synchronously reply.
+ * @param {String} action The action to trigger.
+ * @param {String} data Optional data to send.
+ * @return {*} The response.
+ */
+ requestSync: function(action, data) {
+ var request = document.createTextNode('');
+ request.setUserData('action', action, null);
+ request.setUserData('data', data, null);
+ request.setUserData('sync', true, 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;
+ },
+ /**
+ * Creates an event that the extension is listening for and will
+ * asynchronously respond by calling the callback.
+ * @param {String} action The action to trigger.
+ * @param {String} data Optional data to send.
+ * @param {Function} callback Optional response callback that will be called
+ * with one data argument.
+ */
+ request: function(action, data, callback) {
+ var request = document.createTextNode('');
+ request.setUserData('action', action, null);
+ request.setUserData('data', data, null);
+ request.setUserData('sync', false, null);
+ if (callback) {
+ request.setUserData('callback', callback, null);
+
+ document.addEventListener('pdf.js.response', function listener(event) {
+ var node = event.target,
+ callback = node.getUserData('callback'),
+ response = node.getUserData('response');
+
+ document.documentElement.removeChild(node);
+
+ document.removeEventListener('pdf.js.response', listener, false);
+ return callback(response);
+ }, false);
+ }
+ document.documentElement.appendChild(request);
+
+ var sender = document.createEvent('HTMLEvents');
+ sender.initEvent('pdf.js.message', true, false);
+ return request.dispatchEvent(sender);
+ }
+ };
+})();
+
+// Settings Manager - This is a utility for saving settings
+// 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 {
+ return 'localStorage' in window && window['localStorage'] !== null &&
+ localStorage;
+ } catch (e) {
+ return false;
+ }
+ })();
+
+ var isFirefoxExtension = PDFJS.isFirefoxExtension;
+
+ function Settings(fingerprint) {
+ var database = null;
+ var index;
+ if (isFirefoxExtension)
+ database = FirefoxCom.requestSync('getDatabase', null) || '{}';
+ else if (isLocalStorageEnabled)
+ database = localStorage.getItem('database') || '{}';
+ else
+ return false;
+
+ database = JSON.parse(database);
+ if (!('files' in database))
+ database.files = [];
+ if (database.files.length >= kSettingsMemory)
+ database.files.shift();
+ for (var i = 0, length = database.files.length; i < length; i++) {
+ var branch = database.files[i];
+ if (branch.fingerprint == fingerprint) {
+ index = i;
+ break;
+ }
+ }
+ if (typeof index != 'number')
+ index = database.files.push({fingerprint: fingerprint}) - 1;
+ this.file = database.files[index];
+ this.database = database;
+ }
+
+ Settings.prototype = {
+ set: function settingsSet(name, val) {
+ if (!('file' in this))
+ return false;
+
+ var file = this.file;
+ file[name] = val;
+ var database = JSON.stringify(this.database);
+ if (isFirefoxExtension)
+ FirefoxCom.requestSync('setDatabase', database);
+ else if (isLocalStorageEnabled)
+ localStorage.setItem('database', database);
+ },
+
+ get: function settingsGet(name, defaultValue) {
+ if (!('file' in this))
+ return defaultValue;
+
+ return this.file[name] || defaultValue;
+ }
+ };
+
+ return Settings;
+})();
+
+var cache = new Cache(kCacheSize);
+var renderingQueue = new RenderingQueue();
+var currentPageNumber = 1;
+
+var PDFView = {
+ pages: [],
+ thumbnails: [],
+ currentScale: kUnknownScale,
+ currentScaleValue: null,
+ initialBookmark: document.location.hash.substring(1),
+ startedTextExtraction: false,
+ pageText: [],
+ container: null,
+ initialized: false,
+ fellback: false,
+ pdfDocument: null,
+ // called once when the document is loaded
+ initialize: function pdfViewInitialize() {
+ this.container = document.getElementById('viewer');
+ this.initialized = true;
+ },
+
+ setScale: function pdfViewSetScale(val, resetAutoSettings) {
+ if (val == this.currentScale)
+ return;
+
+ var pages = this.pages;
+ for (var i = 0; i < pages.length; i++)
+ pages[i].update(val * kCssUnits);
+
+ if (this.currentScale != val)
+ this.pages[this.page - 1].scrollIntoView();
+ this.currentScale = val;
+
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('scalechange', false, false, window, 0);
+ event.scale = val;
+ event.resetAutoSettings = resetAutoSettings;
+ window.dispatchEvent(event);
+ },
+
+ parseScale: function pdfViewParseScale(value, resetAutoSettings) {
+ if ('custom' == value)
+ return;
+
+ var scale = parseFloat(value);
+ this.currentScaleValue = value;
+ if (scale) {
+ this.setScale(scale, true);
+ return;
+ }
+
+ var container = this.container;
+ var currentPage = this.pages[this.page - 1];
+ var pageWidthScale = (container.clientWidth - kScrollbarPadding) /
+ currentPage.width * currentPage.scale / kCssUnits;
+ var pageHeightScale = (container.clientHeight - kScrollbarPadding) /
+ currentPage.height * currentPage.scale / kCssUnits;
+ switch (value) {
+ case 'page-actual':
+ this.setScale(1, resetAutoSettings);
+ break;
+ case 'page-width':
+ this.setScale(pageWidthScale, resetAutoSettings);
+ break;
+ case 'page-height':
+ this.setScale(pageHeightScale, resetAutoSettings);
+ break;
+ case 'page-fit':
+ this.setScale(
+ Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
+ break;
+ case 'auto':
+ this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
+ break;
+ }
+
+ selectScaleOption(value);
+ },
+
+ zoomIn: function pdfViewZoomIn() {
+ var newScale = (this.currentScale * kDefaultScaleDelta).toFixed(2);
+ newScale = Math.min(kMaxScale, newScale);
+ this.parseScale(newScale, true);
+ },
+
+ zoomOut: function pdfViewZoomOut() {
+ var newScale = (this.currentScale / kDefaultScaleDelta).toFixed(2);
+ newScale = Math.max(kMinScale, newScale);
+ this.parseScale(newScale, true);
+ },
+
+ set page(val) {
+ var pages = this.pages;
+ var input = document.getElementById('pageNumber');
+ if (!(0 < val && val <= pages.length)) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', false, false, window, 0);
+ event.pageNumber = this.page;
+ window.dispatchEvent(event);
+ return;
+ }
+
+ pages[val - 1].updateStats();
+ currentPageNumber = val;
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', false, false, window, 0);
+ event.pageNumber = val;
+ window.dispatchEvent(event);
+
+ // checking if the this.page was called from the updateViewarea function:
+ // avoiding the creation of two "set page" method (internal and public)
+ if (updateViewarea.inProgress)
+ return;
+
+ // Avoid scrolling the first page during loading
+ if (this.loading && val == 1)
+ return;
+
+ pages[val - 1].scrollIntoView();
+ },
+
+ get page() {
+ return currentPageNumber;
+ },
+
+ open: function pdfViewOpen(url, scale, password) {
+ var parameters = {password: password};
+ if (typeof url === 'string') { // URL
+ this.url = url;
+ document.title = decodeURIComponent(getFileName(url)) || url;
+ parameters.url = url;
+ } else if (url && 'byteLength' in url) { // ArrayBuffer
+ parameters.data = url;
+ }
+
+// if (!PDFView.loadingBar) {
+// PDFView.loadingBar = new ProgressBar('#loadingBar', {});
+// }
+
+ this.container = document.getElementById('viewer');
+
+ this.pdfDocument = null;
+ var self = this;
+ self.loading = true;
+ PDFJS.getDocument(parameters).then(
+ function getDocumentCallback(pdfDocument) {
+ self.load(pdfDocument, scale);
+ self.loading = false;
+ },
+ function getDocumentError(message, exception) {
+ if (exception.name === 'PasswordException') {
+ if (exception.code === 'needpassword') {
+ var promptString = mozL10n.get('request_password', null,
+ 'PDF is protected by a password:');
+ password = prompt(promptString);
+ if (password && password.length > 0) {
+ return PDFView.open(url, scale, password);
+ }
+ }
+ }
+
+ var loadingIndicator = document.getElementById('loading');
+ loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
+ null, 'Error');
+ var moreInfo = {
+ message: message
+ };
+ self.error(mozL10n.get('loading_error', null,
+ 'An error occurred while loading the PDF.'), moreInfo);
+ self.loading = false;
+ },
+ function getDocumentProgress(progressData) {
+ self.progress(progressData.loaded / progressData.total);
+ }
+ );
+ },
+
+ download: function pdfViewDownload() {
+ function noData() {
+ FirefoxCom.request('download', { originalUrl: url });
+ }
+
+ var url = this.url.split('#')[0];
+ if (PDFJS.isFirefoxExtension) {
+ // Document isn't ready just try to download with the url.
+ if (!this.pdfDocument) {
+ noData();
+ return;
+ }
+ this.pdfDocument.getData().then(
+ function getDataSuccess(data) {
+ var bb = new MozBlobBuilder();
+ bb.append(data.buffer);
+ var blobUrl = window.URL.createObjectURL(
+ bb.getBlob('application/pdf'));
+
+ FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url },
+ function response(err) {
+ if (err) {
+ // This error won't really be helpful because it's likely the
+ // fallback won't work either (or is already open).
+ PDFView.error('PDF failed to download.');
+ }
+ window.URL.revokeObjectURL(blobUrl);
+ }
+ );
+ },
+ noData // Error ocurred try downloading with just the url.
+ );
+ } else {
+ url += '#pdfjs.action=download', '_parent';
+ window.open(url, '_parent');
+ }
+ },
+
+ fallback: function pdfViewFallback() {
+ if (!PDFJS.isFirefoxExtension)
+ return;
+ // Only trigger the fallback once so we don't spam the user with messages
+ // for one PDF.
+ if (this.fellback)
+ return;
+ this.fellback = true;
+ var url = this.url.split('#')[0];
+ FirefoxCom.request('fallback', url, function response(download) {
+ if (!download)
+ return;
+ PDFView.download();
+ });
+ },
+
+ navigateTo: function pdfViewNavigateTo(dest) {
+ if (typeof dest === 'string')
+ dest = this.destinations[dest];
+ if (!(dest instanceof Array))
+ return; // invalid destination
+ // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+ var destRef = dest[0];
+ var pageNumber = destRef instanceof Object ?
+ this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
+ if (pageNumber > this.pages.length)
+ pageNumber = this.pages.length;
+ if (pageNumber) {
+ this.page = pageNumber;
+ var currentPage = this.pages[pageNumber - 1];
+ currentPage.scrollIntoView(dest);
+ }
+ },
+
+ getDestinationHash: function pdfViewGetDestinationHash(dest) {
+ if (typeof dest === 'string')
+ 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 = PDFView.getAnchorUrl('#page=' + pageNumber);
+ var destKind = dest[1];
+ 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]) {
+ pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+ }
+ }
+ return pdfOpenParams;
+ }
+ }
+ 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.
+ * @param {Object} moreInfo (optional) Further information about the error
+ * that is more technical. Should have a 'message'
+ * and optionally a 'stack' property.
+ */
+ error: function pdfViewError(message, moreInfo) {
+ var moreInfoText = mozL10n.get('error_build', {build: PDFJS.build},
+ 'PDF.JS Build: {{build}}') + '\n';
+ if (moreInfo) {
+ moreInfoText +=
+ mozL10n.get('error_message', {message: moreInfo.message},
+ 'Message: {{message}}');
+ if (moreInfo.stack) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_stack', {stack: moreInfo.stack},
+ 'Stack: {{stack}}');
+ } else {
+ if (moreInfo.filename) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_file', {file: moreInfo.filename},
+ 'File: {{file}}');
+ }
+ if (moreInfo.lineNumber) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_line', {line: moreInfo.lineNumber},
+ 'Line: {{line}}');
+ }
+ }
+ }
+ if (PDFJS.isFirefoxExtension) {
+ console.error(message + '\n' + moreInfoText);
+ this.fallback();
+ return;
+ }
+ var errorWrapper = document.getElementById('errorWrapper');
+ errorWrapper.removeAttribute('hidden');
+
+ var errorMessage = document.getElementById('errorMessage');
+ errorMessage.textContent = message;
+
+ var closeButton = document.getElementById('errorClose');
+ closeButton.onclick = function() {
+ errorWrapper.setAttribute('hidden', 'true');
+ };
+
+ var errorMoreInfo = document.getElementById('errorMoreInfo');
+ var moreInfoButton = document.getElementById('errorShowMore');
+ var lessInfoButton = document.getElementById('errorShowLess');
+ moreInfoButton.onclick = function() {
+ errorMoreInfo.removeAttribute('hidden');
+ moreInfoButton.setAttribute('hidden', 'true');
+ lessInfoButton.removeAttribute('hidden');
+ };
+ lessInfoButton.onclick = function() {
+ errorMoreInfo.setAttribute('hidden', 'true');
+ moreInfoButton.removeAttribute('hidden');
+ lessInfoButton.setAttribute('hidden', 'true');
+ };
+ moreInfoButton.removeAttribute('hidden');
+ lessInfoButton.setAttribute('hidden', 'true');
+ errorMoreInfo.value = moreInfoText;
+
+ errorMoreInfo.rows = moreInfoText.split('\n').length - 1;
+ },
+
+ progress: function pdfViewProgress(level) {
+ var percent = Math.round(level * 100);
+ var loadingIndicator = document.getElementById('loading');
+ loadingIndicator.textContent = mozL10n.get('loading', {percent: percent},
+ 'Loading... {{percent}}%');
+
+// PDFView.loadingBar.percent = percent;
+ },
+
+ load: function pdfViewLoad(pdfDocument, scale) {
+ function bindOnAfterDraw(pageView, thumbnailView) {
+ // when page is painted, using the image as thumbnail base
+ pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+ thumbnailView.setImage(pageView.canvas);
+ preDraw();
+ };
+ }
+
+ this.pdfDocument = pdfDocument;
+
+// var errorWrapper = document.getElementById('errorWrapper');
+// errorWrapper.setAttribute('hidden', 'true');
+
+ var loadingBox = document.getElementById('loading');
+ loadingBox.setAttribute('hidden', 'true');
+
+// var loadingBox = document.getElementById('loadingBox');
+// loadingBox.setAttribute('hidden', 'true');
+
+// var thumbsView = document.getElementById('thumbnailView');
+// thumbsView.parentNode.scrollTop = 0;
+
+// while (thumbsView.hasChildNodes())
+// thumbsView.removeChild(thumbsView.lastChild);
+
+// if ('_loadingInterval' in thumbsView)
+// clearInterval(thumbsView._loadingInterval);
+
+ var container = document.getElementById('viewer');
+ while (container.hasChildNodes())
+ container.removeChild(container.lastChild);
+
+ var pagesCount = pdfDocument.numPages;
+ var id = pdfDocument.fingerprint;
+ var storedHash = null;
+// document.getElementById('numPages').textContent =
+// mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
+ document.getElementById('numPages').innerHTML = pagesCount;
+ document.getElementById('pageNumber').max = pagesCount;
+ PDFView.documentFingerprint = id;
+ var store = PDFView.store = new Settings(id);
+ if (store.get('exists', false)) {
+ var page = store.get('page', '1');
+ var zoom = store.get('zoom', PDFView.currentScale);
+ var left = store.get('scrollLeft', '0');
+ var top = store.get('scrollTop', '0');
+
+ storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
+ }
+
+ var pages = this.pages = [];
+ this.pageText = [];
+ this.startedTextExtraction = false;
+ var pagesRefMap = {};
+ var thumbnails = this.thumbnails = [];
+ var pagePromises = [];
+ for (var i = 1; i <= pagesCount; i++)
+ pagePromises.push(pdfDocument.getPage(i));
+ var self = this;
+ var pagesPromise = PDFJS.Promise.all(pagePromises);
+ pagesPromise.then(function(promisedPages) {
+ for (var i = 1; i <= pagesCount; i++) {
+ var page = promisedPages[i - 1];
+ var pageView = new PageView(container, page, i, scale,
+ page.stats, self.navigateTo.bind(self));
+// var thumbnailView = new ThumbnailView(thumbsView, page, i);
+// bindOnAfterDraw(pageView, thumbnailView);
+
+ pages.push(pageView);
+// thumbnails.push(thumbnailView);
+ var pageRef = page.ref;
+ pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
+ }
+
+ self.pagesRefMap = pagesRefMap;
+ });
+
+ var destinationsPromise = pdfDocument.getDestinations();
+ destinationsPromise.then(function(destinations) {
+ self.destinations = destinations;
+ });
+
+ // outline and initial view depends on destinations and pagesRefMap
+ PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
+ pdfDocument.getOutline().then(function(outline) {
+ self.outline = new DocumentOutlineView(outline);
+ });
+
+ self.setInitialView(storedHash, scale);
+ });
+
+ pdfDocument.getMetadata().then(function(data) {
+ var info = data.info, metadata = data.metadata;
+ self.documentInfo = info;
+ self.metadata = metadata;
+
+ var pdfTitle;
+ if (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;
+ });
+ },
+
+ setInitialView: function pdfViewSetInitialView(storedHash, scale) {
+ // Reset the current scale, as otherwise the page's scale might not get
+ // updated if the zoom level stayed the same.
+ this.currentScale = 0;
+ this.currentScaleValue = null;
+ if (this.initialBookmark) {
+ this.setHash(this.initialBookmark);
+ this.initialBookmark = null;
+ }
+ else if (storedHash)
+ this.setHash(storedHash);
+ 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);
+ }
+ },
+
+ search: function pdfViewStartSearch() {
+ // Limit this function to run every <SEARCH_TIMEOUT>ms.
+ var SEARCH_TIMEOUT = 250;
+ var lastSeach = this.lastSearch;
+ var now = Date.now();
+ if (lastSeach && (now - lastSeach) < SEARCH_TIMEOUT) {
+ if (!this.searchTimer) {
+ this.searchTimer = setTimeout(function resumeSearch() {
+ PDFView.search();
+ },
+ SEARCH_TIMEOUT - (now - lastSeach)
+ );
+ }
+ return;
+ }
+ this.searchTimer = null;
+ this.lastSearch = now;
+
+ function bindLink(link, pageNumber) {
+ link.href = '#' + pageNumber;
+ link.onclick = function searchBindLink() {
+ PDFView.page = pageNumber;
+ return false;
+ };
+ }
+
+ var searchResults = document.getElementById('searchResults');
+
+ var searchTermsInput = document.getElementById('searchTermsInput');
+ searchResults.removeAttribute('hidden');
+ searchResults.textContent = '';
+
+ var terms = searchTermsInput.value;
+
+ if (!terms)
+ return;
+
+ // simple search: removing spaces and hyphens, then scanning every
+ terms = terms.replace(/\s-/g, '').toLowerCase();
+ var index = PDFView.pageText;
+ var pageFound = false;
+ for (var i = 0, ii = index.length; i < ii; i++) {
+ var pageText = index[i].replace(/\s-/g, '').toLowerCase();
+ var j = pageText.indexOf(terms);
+ if (j < 0)
+ continue;
+
+ var pageNumber = i + 1;
+ var textSample = index[i].substr(j, 50);
+ var link = document.createElement('a');
+ bindLink(link, pageNumber);
+ link.textContent = 'Page ' + pageNumber + ': ' + textSample;
+ searchResults.appendChild(link);
+
+ pageFound = true;
+ }
+ if (!pageFound) {
+ searchResults.textContent = mozL10n.get('search_terms_not_found', null,
+ '(Not found)');
+ }
+ },
+
+ setHash: function pdfViewSetHash(hash) {
+ if (!hash)
+ return;
+
+ if (hash.indexOf('=') >= 0) {
+ var params = PDFView.parseQueryString(hash);
+ // borrowing syntax from "Parameters for Opening PDF Files"
+ if ('nameddest' in params) {
+ PDFView.navigateTo(params.nameddest);
+ return;
+ }
+ if ('page' in params) {
+ var pageNumber = (params.page | 0) || 1;
+ this.page = pageNumber;
+ if ('zoom' in params) {
+ var zoomArgs = params.zoom.split(','); // scale,left,top
+ // building destination array
+
+ // If the zoom value, it has to get divided by 100. If it is a string,
+ // it should stay as it is.
+ var zoomArg = zoomArgs[0];
+ var zoomArgNumber = parseFloat(zoomArg);
+ if (zoomArgNumber)
+ zoomArg = zoomArgNumber / 100;
+
+ var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
+ (zoomArgs[2] | 0), zoomArg];
+ var currentPage = this.pages[pageNumber - 1];
+ currentPage.scrollIntoView(dest);
+ } else
+ this.page = params.page; // simple page
+ return;
+ }
+ } else if (/^\d+$/.test(hash)) // page number
+ this.page = hash;
+ else // named destination
+ PDFView.navigateTo(unescape(hash));
+ },
+
+ switchSidebarView: function pdfViewSwitchSidebarView(view) {
+ var thumbsView = document.getElementById('thumbnailView');
+ var outlineView = document.getElementById('outlineView');
+ var searchView = document.getElementById('searchView');
+
+ var thumbsButton = document.getElementById('viewThumbnail');
+ var outlineButton = document.getElementById('viewOutline');
+ var searchButton = document.getElementById('viewSearch');
+
+ switch (view) {
+ case 'thumbs':
+ thumbsButton.classList.add('toggled');
+ outlineButton.classList.remove('toggled');
+ searchButton.classList.remove('toggled');
+ thumbsView.classList.remove('hidden');
+ outlineView.classList.add('hidden');
+ searchView.classList.add('hidden');
+
+ updateThumbViewArea();
+ break;
+
+ case 'outline':
+ thumbsButton.classList.remove('toggled');
+ outlineButton.classList.add('toggled');
+ searchButton.classList.remove('toggled');
+ thumbsView.classList.add('hidden');
+ outlineView.classList.remove('hidden');
+ searchView.classList.add('hidden');
+
+ if (outlineButton.getAttribute('disabled'))
+ return;
+ break;
+
+ case 'search':
+ thumbsButton.classList.remove('toggled');
+ outlineButton.classList.remove('toggled');
+ searchButton.classList.add('toggled');
+ thumbsView.classList.add('hidden');
+ outlineView.classList.add('hidden');
+ searchView.classList.remove('hidden');
+
+ var searchTermsInput = document.getElementById('searchTermsInput');
+ searchTermsInput.focus();
+ // Start text extraction as soon as the search gets displayed.
+ this.extractText();
+ break;
+ }
+ },
+
+ extractText: function() {
+ if (this.startedTextExtraction)
+ return;
+ this.startedTextExtraction = true;
+ var self = this;
+ function extractPageText(pageIndex) {
+ self.pages[pageIndex].pdfPage.getTextContent().then(
+ function textContentResolved(textContent) {
+ self.pageText[pageIndex] = textContent;
+ self.search();
+ if ((pageIndex + 1) < self.pages.length)
+ extractPageText(pageIndex + 1);
+ }
+ );
+ };
+ extractPageText(0);
+ },
+
+ getVisiblePages: function pdfViewGetVisiblePages() {
+ var pages = this.pages;
+ var kBottomMargin = 10;
+ var kTopPadding = 30;
+ var visiblePages = [];
+
+ var currentHeight = kTopPadding + kBottomMargin;
+ //var container = this.container;
+ var container = document.getElementById('viewer');
+
+ // Add 1px to the scrolltop to give a little wiggle room if the math is off,
+ // this won't be needed if we calc current page number based off the middle
+ // of the screen instead of the top.
+ var containerTop = container.scrollTop + 1;
+ for (var i = 1; i <= pages.length; ++i) {
+ var page = pages[i - 1];
+ var pageHeight = page.height + kBottomMargin;
+ if (currentHeight + pageHeight > containerTop)
+ break;
+
+ currentHeight += pageHeight;
+ }
+
+ var containerBottom = containerTop + container.clientHeight;
+ for (; i <= pages.length && currentHeight < containerBottom; ++i) {
+ var singlePage = pages[i - 1];
+ visiblePages.push({ id: singlePage.id, y: currentHeight,
+ view: singlePage });
+ currentHeight += page.height + kBottomMargin;
+ }
+ return visiblePages;
+ },
+
+ getVisibleThumbs: function pdfViewGetVisibleThumbs() {
+ var thumbs = this.thumbnails;
+ var kBottomMargin = 15;
+ var visibleThumbs = [];
+
+ var view = document.getElementById('thumbnailView');
+ var currentHeight = kBottomMargin;
+
+ var top = view.scrollTop;
+ for (var i = 1; i <= thumbs.length; ++i) {
+ var thumb = thumbs[i - 1];
+ var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
+ if (currentHeight + thumbHeight > top)
+ break;
+
+ currentHeight += thumbHeight;
+ }
+
+ var bottom = top + view.clientHeight;
+ for (; i <= thumbs.length && currentHeight < bottom; ++i) {
+ var singleThumb = thumbs[i - 1];
+ visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
+ view: singleThumb });
+ currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
+ }
+
+ 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;
+ }
+};
+
+var PageView = function pageView(container, pdfPage, id, scale,
+ stats, navigateTo) {
+ this.id = id;
+ this.pdfPage = pdfPage;
+
+ this.scale = scale || 1.0;
+ this.viewport = this.pdfPage.getViewport(this.scale);
+
+ var anchor = document.createElement('a');
+ anchor.name = '' + this.id;
+
+ var div = document.createElement('div');
+ div.id = 'pageContainer' + this.id;
+ div.className = 'page';
+
+ container.appendChild(anchor);
+ container.appendChild(div);
+
+ this.destroy = function pageViewDestroy() {
+ this.update();
+ this.pdfPage.destroy();
+ };
+
+ this.update = function pageViewUpdate(scale) {
+ this.scale = scale || this.scale;
+ var viewport = this.pdfPage.getViewport(this.scale);
+
+ this.viewport = viewport;
+ div.style.width = viewport.width + 'px';
+ div.style.height = viewport.height + 'px';
+
+ while (div.hasChildNodes())
+ div.removeChild(div.lastChild);
+ div.removeAttribute('data-loaded');
+
+ delete this.canvas;
+
+ this.loadingIconDiv = document.createElement('div');
+ this.loadingIconDiv.className = 'loadingIcon';
+ div.appendChild(this.loadingIconDiv);
+ };
+
+ Object.defineProperty(this, 'width', {
+ get: function PageView_getWidth() {
+ return this.viewport.width;
+ },
+ enumerable: true
+ });
+
+ Object.defineProperty(this, 'height', {
+ get: function PageView_getHeight() {
+ return this.viewport.height;
+ },
+ enumerable: true
+ });
+
+ function setupAnnotations(pdfPage, viewport) {
+ function bindLink(link, dest) {
+ link.href = PDFView.getDestinationHash(dest);
+ link.onclick = function pageViewSetupLinksOnclick() {
+ if (dest)
+ PDFView.navigateTo(dest);
+ return false;
+ };
+ }
+ function createElementWithStyle(tagName, item) {
+ var rect = viewport.convertToViewportRectangle(item.rect);
+ rect = PDFJS.Util.normalizeRect(rect);
+ var element = document.createElement(tagName);
+ element.style.left = Math.floor(rect[0]) + 'px';
+ element.style.top = Math.floor(rect[1]) + 'px';
+ element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+ element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+ return element;
+ }
+ function createCommentAnnotation(type, item) {
+ var container = document.createElement('section');
+ container.className = 'annotComment';
+
+ var image = createElementWithStyle('img', item);
+ var type = item.type;
+ var rect = viewport.convertToViewportRectangle(item.rect);
+ rect = PDFJS.Util.normalizeRect(rect);
+ image.src = kImageDirectory + 'annotation-' + type.toLowerCase() + '.svg';
+ image.alt = mozL10n.get('text_annotation_type', {type: type},
+ '[{{type}} Annotation]');
+ var content = document.createElement('div');
+ content.setAttribute('hidden', true);
+ var title = document.createElement('h1');
+ var text = document.createElement('p');
+ content.style.left = Math.floor(rect[2]) + 'px';
+ content.style.top = Math.floor(rect[1]) + 'px';
+ title.textContent = item.title;
+
+ if (!item.content && !item.title) {
+ content.setAttribute('hidden', true);
+ } else {
+ 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() {
+ content.removeAttribute('hidden');
+ }, false);
+
+ image.addEventListener('mouseout', function annotationImageOut() {
+ content.setAttribute('hidden', true);
+ }, false);
+ }
+
+ content.appendChild(title);
+ content.appendChild(text);
+ container.appendChild(image);
+ container.appendChild(content);
+
+ return container;
+ }
+
+ pdfPage.getAnnotations().then(function(items) {
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ switch (item.type) {
+ case 'Link':
+ var link = createElementWithStyle('a', item);
+ link.href = item.url || '';
+ if (!item.url)
+ bindLink(link, ('dest' in item) ? item.dest : null);
+ div.appendChild(link);
+ break;
+ case 'Text':
+ var comment = createCommentAnnotation(item.name, item);
+ if (comment)
+ div.appendChild(comment);
+ break;
+ case 'Widget':
+ // TODO: support forms
+ PDFView.fallback();
+ break;
+ }
+ }
+ });
+ }
+
+ this.getPagePoint = function pageViewGetPagePoint(x, y) {
+ return this.viewport.convertToPdfPoint(x, y);
+ };
+
+ this.scrollIntoView = function pageViewScrollIntoView(dest) {
+ if (!dest) {
+ div.scrollIntoView(true);
+ return;
+ }
+
+ var x = 0, y = 0;
+ var width = 0, height = 0, widthScale, heightScale;
+ var scale = 0;
+ switch (dest[1].name) {
+ case 'XYZ':
+ x = dest[2];
+ y = dest[3];
+ scale = dest[4];
+ break;
+ case 'Fit':
+ case 'FitB':
+ scale = 'page-fit';
+ break;
+ case 'FitH':
+ case 'FitBH':
+ y = dest[2];
+ scale = 'page-width';
+ break;
+ case 'FitV':
+ case 'FitBV':
+ x = dest[2];
+ scale = 'page-height';
+ break;
+ case 'FitR':
+ x = dest[2];
+ y = dest[3];
+ width = dest[4] - x;
+ height = dest[5] - y;
+ widthScale = (this.container.clientWidth - kScrollbarPadding) /
+ width / kCssUnits;
+ heightScale = (this.container.clientHeight - kScrollbarPadding) /
+ height / kCssUnits;
+ scale = Math.min(widthScale, heightScale);
+ break;
+ default:
+ return;
+ }
+
+ if (scale && scale !== PDFView.currentScale)
+ PDFView.parseScale(scale, true);
+ else if (PDFView.currentScale === kUnknownScale)
+ PDFView.parseScale(kDefaultScale, true);
+
+ var boundingRect = [
+ this.viewport.convertToViewportPoint(x, y),
+ this.viewport.convertToViewportPoint(x + width, y + height)
+ ];
+ setTimeout(function pageViewScrollIntoViewRelayout() {
+ // letting page to re-layout before scrolling
+ var scale = PDFView.currentScale;
+ var x = Math.min(boundingRect[0][0], boundingRect[1][0]);
+ var y = Math.min(boundingRect[0][1], boundingRect[1][1]);
+ var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
+ var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
+
+ // using temporary div to scroll it into view
+ var tempDiv = document.createElement('div');
+ tempDiv.style.position = 'absolute';
+ tempDiv.style.left = Math.floor(x) + 'px';
+ tempDiv.style.top = Math.floor(y) + 'px';
+ tempDiv.style.width = Math.ceil(width) + 'px';
+ tempDiv.style.height = Math.ceil(height) + 'px';
+ div.appendChild(tempDiv);
+ tempDiv.scrollIntoView(true);
+ div.removeChild(tempDiv);
+ }, 0);
+ };
+
+ this.drawingRequired = function() {
+ return !div.querySelector('canvas');
+ };
+
+ this.draw = function pageviewDraw(callback) {
+ if (!this.drawingRequired()) {
+ this.updateStats();
+ callback();
+ return;
+ }
+
+ var canvas = document.createElement('canvas');
+ canvas.id = 'page' + this.id;
+ canvas.mozOpaque = true;
+ div.appendChild(canvas);
+ this.canvas = canvas;
+
+ var textLayerDiv = null;
+ if (!PDFJS.disableTextLayer) {
+ textLayerDiv = document.createElement('div');
+ textLayerDiv.className = 'textLayer';
+ div.appendChild(textLayerDiv);
+ }
+ var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
+
+ var scale = this.scale, viewport = this.viewport;
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+
+ // Rendering area
+
+ var self = this;
+ function pageViewDrawCallback(error) {
+ if (self.loadingIconDiv) {
+ div.removeChild(self.loadingIconDiv);
+ delete self.loadingIconDiv;
+ }
+
+ if (error) {
+ PDFView.error(mozL10n.get('rendering_error', null,
+ 'An error occurred while rendering the page.'), error);
+ }
+
+ self.stats = pdfPage.stats;
+ self.updateStats();
+ if (self.onAfterDraw)
+ self.onAfterDraw();
+
+ cache.push(self);
+ callback();
+ }
+
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: this.viewport,
+ textLayer: textLayer
+ };
+ this.pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ pageViewDrawCallback(null);
+ },
+ function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ }
+ );
+
+ setupAnnotations(this.pdfPage, this.viewport);
+ div.setAttribute('data-loaded', true);
+ };
+
+ this.updateStats = function pageViewUpdateStats() {
+ if (PDFJS.pdfBug && Stats.enabled) {
+ var stats = this.stats;
+ Stats.add(this.id, stats);
+ }
+ };
+};
+
+var ThumbnailView = function thumbnailView(container, pdfPage, id) {
+ var anchor = document.createElement('a');
+ anchor.href = PDFView.getAnchorUrl('#page=' + id);
+ anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
+ anchor.onclick = function stopNavigation() {
+ PDFView.page = id;
+ return false;
+ };
+
+ var viewport = pdfPage.getViewport(1);
+ var pageWidth = this.width = viewport.width;
+ var pageHeight = this.height = viewport.height;
+ var pageRatio = pageWidth / pageHeight;
+ this.id = id;
+
+ var canvasWidth = 98;
+ var canvasHeight = canvasWidth / this.width * this.height;
+ var scaleX = this.scaleX = (canvasWidth / pageWidth);
+ var scaleY = this.scaleY = (canvasHeight / pageHeight);
+
+ var div = document.createElement('div');
+ div.id = 'thumbnailContainer' + id;
+ div.className = 'thumbnail';
+
+ anchor.appendChild(div);
+ container.appendChild(anchor);
+
+ this.hasImage = false;
+
+ function getPageDrawContext() {
+ var canvas = document.createElement('canvas');
+ canvas.id = 'thumbnail' + id;
+ canvas.mozOpaque = true;
+
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+ canvas.className = 'thumbnailImage';
+ canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+ {page: id}, 'Thumbnail of Page {{page}}'));
+
+ div.setAttribute('data-loaded', true);
+
+ var ring = document.createElement('div');
+ ring.className = 'thumbnailSelectionRing';
+ ring.appendChild(canvas);
+ div.appendChild(ring);
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+ ctx.restore();
+ return ctx;
+ }
+
+ this.drawingRequired = function thumbnailViewDrawingRequired() {
+ return !this.hasImage;
+ };
+
+ this.draw = function thumbnailViewDraw(callback) {
+ if (this.hasImage) {
+ callback();
+ return;
+ }
+
+ var ctx = getPageDrawContext();
+ var drawViewport = pdfPage.getViewport(scaleX);
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport
+ };
+ pdfPage.render(renderContext).then(
+ function pdfPageRenderCallback() {
+ callback();
+ },
+ function pdfPageRenderError(error) {
+ callback();
+ }
+ );
+ this.hasImage = true;
+ };
+
+ this.setImage = function thumbnailViewSetImage(img) {
+ if (this.hasImage || !img)
+ return;
+
+ var ctx = getPageDrawContext();
+ ctx.drawImage(img, 0, 0, img.width, img.height,
+ 0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ this.hasImage = true;
+ };
+};
+
+var DocumentOutlineView = function documentOutlineView(outline) {
+ var outlineView = document.getElementById('outlineView');
+// while (outlineView.firstChild)
+// outlineView.removeChild(outlineView.firstChild);
+
+ function bindItemLink(domObj, item) {
+ domObj.href = PDFView.getDestinationHash(item.dest);
+ domObj.onclick = function documentOutlineViewOnclick(e) {
+ PDFView.navigateTo(item.dest);
+ return false;
+ };
+ }
+
+ if (!outline) {
+ var noOutline = document.createElement('div');
+ noOutline.classList.add('noOutline');
+ //noOutline.textContent = mozL10n.get('no_outline', null,
+ // 'No Outline Available');
+ noOutline.textContent = 'No Outline Available';
+ //outlineView.appendChild(noOutline);
+ return;
+ }
+
+ var queue = [{parent: outlineView, items: outline}];
+ while (queue.length > 0) {
+ var levelData = queue.shift();
+ var i, n = levelData.items.length;
+ for (i = 0; i < n; i++) {
+ var item = levelData.items[i];
+ var div = document.createElement('div');
+ div.className = 'outlineItem';
+ var a = document.createElement('a');
+ bindItemLink(a, item);
+ a.textContent = item.title;
+ div.appendChild(a);
+
+ if (item.items.length > 0) {
+ var itemsDiv = document.createElement('div');
+ itemsDiv.className = 'outlineItems';
+ div.appendChild(itemsDiv);
+ queue.push({parent: itemsDiv, items: item.items});
+ }
+
+ levelData.parent.appendChild(div);
+ }
+ }
+};
+
+// 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;
+
+ this.beginLayout = function textLayerBuilderBeginLayout() {
+ this.textDivs = [];
+ this.textLayerQueue = [];
+ };
+
+ this.endLayout = function textLayerBuilderEndLayout() {
+ var self = this;
+ var textDivs = this.textDivs;
+ var textLayerDiv = this.textLayerDiv;
+ var renderTimer = null;
+ var renderingDone = false;
+ var renderInterval = 0;
+ var resumeInterval = 500; // in ms
+
+ // Render the text layer, one div at a time
+ function renderTextLayer() {
+ if (textDivs.length === 0) {
+ clearInterval(renderTimer);
+ renderingDone = true;
+ return;
+ }
+ var textDiv = textDivs.shift();
+ if (textDiv.dataset.textLength > 0) {
+ textLayerDiv.appendChild(textDiv);
+
+ if (textDiv.dataset.textLength > 1) { // avoid div by zero
+ // Adjust div width to match canvas text
+ // Due to the .offsetWidth calls, this is slow
+ // This needs to come after appending to the DOM
+ var textScale = textDiv.dataset.canvasWidth / textDiv.offsetWidth;
+ CustomStyle.setProp('transform' , textDiv,
+ 'scale(' + textScale + ', 1)');
+ CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
+ }
+ } // textLength > 0
+ }
+ renderTimer = setInterval(renderTextLayer, renderInterval);
+
+ // Stop rendering when user scrolls. Resume after XXX milliseconds
+ // of no scroll events
+ var scrollTimer = null;
+ function textLayerOnScroll() {
+ if (renderingDone) {
+ window.removeEventListener('scroll', textLayerOnScroll, false);
+ return;
+ }
+
+ // Immediately pause rendering
+ clearInterval(renderTimer);
+
+ clearTimeout(scrollTimer);
+ scrollTimer = setTimeout(function textLayerScrollTimer() {
+ // Resume rendering
+ renderTimer = setInterval(renderTextLayer, renderInterval);
+ }, resumeInterval);
+ }; // textLayerOnScroll
+
+ window.addEventListener('scroll', textLayerOnScroll, false);
+ }; // endLayout
+
+ this.appendText = function textLayerBuilderAppendText(text,
+ fontName, fontSize) {
+ var textDiv = document.createElement('div');
+
+ // 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.left = text.geom.x + 'px';
+ textDiv.style.top = (text.geom.y - fontHeight) + 'px';
+ 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) {
+ PDFView.initialize();
+ var params = PDFView.parseQueryString(document.location.search.substring(1));
+
+ var file = PDFJS.isFirefoxExtension ?
+ window.location.toString() : params.file || kDefaultURL;
+
+/* if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
+ !window.FileList || !window.Blob) {
+ document.getElementById('openFile').setAttribute('hidden', 'true');
+ } else {
+ document.getElementById('fileInput').value = null;
+ }
+*/
+ // Special debugging flags in the hash section of the URL.
+ var hash = document.location.hash.substring(1);
+ var hashParams = PDFView.parseQueryString(hash);
+
+ if ('disableWorker' in hashParams)
+ PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
+
+/* if (!PDFJS.isFirefoxExtension) {
+ var locale = navigator.language;
+ if ('locale' in hashParams)
+ locale = hashParams['locale'];
+ mozL10n.language.code = locale;
+ }
+*/
+ if ('disableTextLayer' in hashParams)
+ PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true');
+
+ if ('pdfBug' in hashParams &&
+ (!PDFJS.isFirefoxExtension || FirefoxCom.requestSync('pdfBugEnabled'))) {
+ PDFJS.pdfBug = true;
+ var pdfBug = hashParams['pdfBug'];
+ var enabled = pdfBug.split(',');
+ PDFBug.enable(enabled);
+ PDFBug.init();
+ }
+
+/* if (!PDFJS.isFirefoxExtension ||
+ (PDFJS.isFirefoxExtension && FirefoxCom.requestSync('searchEnabled'))) {
+ document.querySelector('#viewSearch').classList.remove('hidden');
+ }
+*/
+ // Listen for warnings to trigger the fallback UI. Errors should be caught
+ // and call PDFView.error() so we don't need to listen for those.
+ PDFJS.LogManager.addLogger({
+ warn: function() {
+ PDFView.fallback();
+ }
+ });
+
+// var thumbsView = document.getElementById('thumbnailView');
+// thumbsView.addEventListener('scroll', updateThumbViewArea, true);
+/*
+ var mainContainer = document.getElementById('mainContainer');
+ var outerContainer = document.getElementById('outerContainer');
+ mainContainer.addEventListener('transitionend', function(e) {
+ if (e.target == mainContainer) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('resize', false, false, window, 0);
+ window.dispatchEvent(event);
+ outerContainer.classList.remove('sidebarMoving');
+ }
+ }, true);
+
+ document.getElementById('sidebarToggle').addEventListener('click',
+ function() {
+ this.classList.toggle('toggled');
+ outerContainer.classList.add('sidebarMoving');
+ outerContainer.classList.toggle('sidebarOpen');
+ updateThumbViewArea();
+ });
+
+ PDFView.open(file, 0);
+*/
+}, true);
+
+/**
+ * Render the next not yet visible page already such that it is
+ * hopefully ready once the user scrolls to it.
+ */
+function preDraw() {
+ var pages = PDFView.pages;
+ var visible = PDFView.getVisiblePages();
+ var last = visible[visible.length - 1];
+ // PageView.id is the actual page number, which is + 1 compared
+ // to the index in `pages`. That means, pages[last.id] is the next
+ // PageView instance.
+ if (pages[last.id] && pages[last.id].drawingRequired()) {
+ renderingQueue.enqueueDraw(pages[last.id]);
+ return;
+ }
+ // If there is nothing to draw on the next page, maybe the user
+ // is scrolling up, so, let's try to render the next page *before*
+ // the first visible page
+ if (pages[visible[0].id - 2]) {
+ renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
+ }
+}
+
+function updateViewarea() {
+ if (!PDFView.initialized)
+ return;
+ var visiblePages = PDFView.getVisiblePages();
+ var pageToDraw;
+ for (var i = 0; i < visiblePages.length; i++) {
+ var page = visiblePages[i];
+ var pageObj = PDFView.pages[page.id - 1];
+
+ pageToDraw |= pageObj.drawingRequired();
+ renderingQueue.enqueueDraw(pageObj);
+ }
+
+ if (!visiblePages.length)
+ return;
+
+ // If there is no need to draw a page that is currenlty visible, preDraw the
+ // next page the user might scroll to.
+ if (!pageToDraw) {
+ preDraw();
+ }
+
+ updateViewarea.inProgress = true; // used in "set page"
+ var currentId = PDFView.page;
+ var firstPage = visiblePages[0];
+ PDFView.page = firstPage.id;
+ updateViewarea.inProgress = false;
+
+ var currentScale = PDFView.currentScale;
+ var currentScaleValue = PDFView.currentScaleValue;
+ var normalizedScaleValue = currentScaleValue == currentScale ?
+ currentScale * 100 : currentScaleValue;
+
+ var pageNumber = firstPage.id;
+ var pdfOpenParams = '#page=' + pageNumber;
+ pdfOpenParams += '&zoom=' + normalizedScaleValue;
+ var currentPage = PDFView.pages[pageNumber - 1];
+ var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft,
+ (PDFView.container.scrollTop - firstPage.y));
+ pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
+
+ var store = PDFView.store;
+ store.set('exists', true);
+ store.set('page', pageNumber);
+ store.set('zoom', normalizedScaleValue);
+ store.set('scrollLeft', Math.round(topLeft[0]));
+ store.set('scrollTop', Math.round(topLeft[1]));
+// var href = PDFView.getAnchorUrl(pdfOpenParams);
+// document.getElementById('viewBookmark').href = href;
+}
+
+window.addEventListener('scroll', function webViewerScroll(evt) {
+ updateViewarea();
+}, true);
+
+var thumbnailTimer;
+
+function updateThumbViewArea() {
+ // Only render thumbs after pausing scrolling for this amount of time
+ // (makes UI more responsive)
+ var delay = 50; // in ms
+
+ if (thumbnailTimer)
+ clearTimeout(thumbnailTimer);
+
+ thumbnailTimer = setTimeout(function() {
+ var visibleThumbs = PDFView.getVisibleThumbs();
+ for (var i = 0; i < visibleThumbs.length; i++) {
+ var thumb = visibleThumbs[i];
+ renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
+ }
+ }, delay);
+}
+
+window.addEventListener('resize', function webViewerResize(evt) {
+ if (PDFView.initialized &&
+ (document.getElementById('pageWidthOption').selected ||
+ document.getElementById('pageFitOption').selected ||
+ document.getElementById('pageAutoOption').selected))
+ PDFView.parseScale(document.getElementById('scaleSelect').value);
+ updateViewarea();
+});
+
+window.addEventListener('hashchange', function webViewerHashchange(evt) {
+ PDFView.setHash(document.location.hash.substring(1));
+});
+
+window.addEventListener('change', function webViewerChange(evt) {
+ var files = evt.target.files;
+ if (!files || files.length == 0)
+ return;
+
+ // Read the local file into a Uint8Array.
+ var fileReader = new FileReader();
+ fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
+ var data = evt.target.result;
+ var buffer = new ArrayBuffer(data.length);
+ var uint8Array = new Uint8Array(buffer);
+
+ for (var i = 0; i < data.length; i++)
+ uint8Array[i] = data.charCodeAt(i);
+
+ PDFView.open(uint8Array, 0);
+ };
+
+ // Read as a binary string since "readAsArrayBuffer" is not yet
+ // implemented in Firefox.
+ var file = files[0];
+ fileReader.readAsBinaryString(file);
+ document.title = file.name;
+
+ // URL does not reflect proper document location - hiding some icons.
+ document.getElementById('viewBookmark').setAttribute('hidden', 'true');
+ document.getElementById('download').setAttribute('hidden', 'true');
+}, true);
+
+function selectScaleOption(value) {
+ var options = document.getElementById('scaleSelect').options;
+ var predefinedValueFound = false;
+ for (var i = 0; i < options.length; i++) {
+ var option = options[i];
+ if (option.value != value) {
+ option.selected = false;
+ continue;
+ }
+ option.selected = true;
+ predefinedValueFound = true;
+ }
+ return predefinedValueFound;
+}
+
+window.addEventListener('localized', function localized(evt) {
+ document.getElementsByTagName('html')[0].dir = mozL10n.language.direction;
+}, true);
+
+window.addEventListener('scalechange', function scalechange(evt) {
+ var customScaleOption = document.getElementById('customScaleOption');
+ customScaleOption.selected = false;
+
+ if (!evt.resetAutoSettings &&
+ (document.getElementById('pageWidthOption').selected ||
+ document.getElementById('pageFitOption').selected ||
+ document.getElementById('pageAutoOption').selected)) {
+ updateViewarea();
+ return;
+ }
+
+ var predefinedValueFound = selectScaleOption('' + evt.scale);
+ if (!predefinedValueFound) {
+ customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
+ customScaleOption.selected = true;
+ }
+
+ updateViewarea();
+}, true);
+
+window.addEventListener('pagechange', function pagechange(evt) {
+ var page = evt.pageNumber;
+ if (document.getElementById('pageNumber').value != page) {
+ document.getElementById('pageNumber').value = page;
+ var selected = document.querySelector('.thumbnail.selected');
+ if (selected)
+ selected.classList.remove('selected');
+/*
+ var thumbnail = document.getElementById('thumbnailContainer' + page);
+ thumbnail.classList.add('selected');
+ var visibleThumbs = PDFView.getVisibleThumbs();
+ var numVisibleThumbs = visibleThumbs.length;
+ // If the thumbnail isn't currently visible scroll it into view.
+ if (numVisibleThumbs > 0) {
+ var first = visibleThumbs[0].id;
+ // Account for only one thumbnail being visible.
+ var last = numVisibleThumbs > 1 ?
+ visibleThumbs[numVisibleThumbs - 1].id : first;
+ if (page <= first || page >= last)
+ thumbnail.scrollIntoView();
+ }
+*/
+ }
+ document.getElementById('previous').disabled = (page <= 1);
+ document.getElementById('next').disabled = (page >= PDFView.pages.length);
+}, true);
+
+// Firefox specific event, so that we can prevent browser from zooming
+window.addEventListener('DOMMouseScroll', function(evt) {
+ if (evt.ctrlKey) {
+ evt.preventDefault();
+
+ var ticks = evt.detail;
+ var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
+ for (var i = 0, length = Math.abs(ticks); i < length; i++)
+ PDFView[direction]();
+ }
+}, false);
+
+window.addEventListener('keydown', function keydown(evt) {
+ var handled = false;
+ var cmd = (evt.ctrlKey ? 1 : 0) |
+ (evt.altKey ? 2 : 0) |
+ (evt.shiftKey ? 4 : 0) |
+ (evt.metaKey ? 8 : 0);
+
+ // First, handle the key bindings that are independent whether an input
+ // control is selected or not.
+ if (cmd == 1 || cmd == 8) { // either CTRL or META key.
+ switch (evt.keyCode) {
+ case 61: // FF/Mac '='
+ case 107: // FF '+' and '='
+ case 187: // Chrome '+'
+ PDFView.zoomIn();
+ handled = true;
+ break;
+ case 109: // FF '-'
+ case 189: // Chrome '-'
+ PDFView.zoomOut();
+ handled = true;
+ break;
+ case 48: // '0'
+ PDFView.parseScale(kDefaultScale, true);
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled) {
+ evt.preventDefault();
+ return;
+ }
+
+ // Some shortcuts should not get handled if a control/input element
+ // is selected.
+ var curElement = document.activeElement;
+ if (curElement && curElement.tagName == 'INPUT')
+ return;
+ var controlsElement = document.getElementById('controls');
+ while (curElement) {
+ if (curElement === controlsElement)
+ return; // ignoring if the 'controls' element is focused
+ curElement = curElement.parentNode;
+ }
+
+ if (cmd == 0) { // no control key pressed at all.
+ switch (evt.keyCode) {
+ case 37: // left arrow
+ case 75: // 'k'
+ case 80: // 'p'
+ PDFView.page--;
+ handled = true;
+ break;
+ case 39: // right arrow
+ case 74: // 'j'
+ case 78: // 'n'
+ PDFView.page++;
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled) {
+ evt.preventDefault();
+ }
+});
diff --git a/apps/files_pdfviewer/js/viewer.js b/apps/files_pdfviewer/js/viewer.js
index 85bbf09362e..d98bedc5c09 100644
--- a/apps/files_pdfviewer/js/viewer.js
+++ b/apps/files_pdfviewer/js/viewer.js
@@ -24,21 +24,9 @@ function showPDFviewer(dir,filename){
var oldcontent = $("#content").html();
$("#content").html(oldcontent+'<div id="loading">Loading... 0%</div><div id="viewer"></div>');
showPDFviewer.lastTitle = document.title;
- if(!showPDFviewer.loaded){
- OC.addScript( 'files_pdfviewer', 'pdfjs/build/pdf',function(){
- OC.addScript( 'files_pdfviewer', 'pdfview',function(){
- showPDFviewer.loaded=true;
- PDFJS.workerSrc = OC.filePath('files_pdfviewer','js','pdfjs/build/pdf.js');
- PDFView.Ptitle = filename;
- PDFView.open(url,1.00);
- PDFView.active=true;
- });
- });
- }else{
- PDFView.Ptitle = filename;
- PDFView.open(url,1.00);
- PDFView.active=true;
- }
+ PDFView.Ptitle = filename;
+ PDFView.open(url,1.00);
+ PDFView.active=true;
$("#pageWidthOption").attr("selected","selected");
showPDFviewer.shown = true;
}
@@ -46,7 +34,6 @@ function showPDFviewer(dir,filename){
showPDFviewer.shown=false;
showPDFviewer.oldCode='';
showPDFviewer.lastTitle='';
-showPDFviewer.loaded=false;
$(document).ready(function(){
if(!$.browser.msie){//doesnt work on IE
diff --git a/apps/files_sharing/appinfo/database.xml b/apps/files_sharing/appinfo/database.xml
index 3378b6b09e5..c5cb632d4fe 100644
--- a/apps/files_sharing/appinfo/database.xml
+++ b/apps/files_sharing/appinfo/database.xml
@@ -3,7 +3,7 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
<name>*dbprefix*sharing</name>
<declaration>
diff --git a/apps/files_sharing/lib_share.php b/apps/files_sharing/lib_share.php
index cf5456d7042..356c958b6ce 100644
--- a/apps/files_sharing/lib_share.php
+++ b/apps/files_sharing/lib_share.php
@@ -104,10 +104,6 @@ class OC_Share {
$counter++;
}
}
- if (isset($gid)) {
- $uid = $uid."@".$gid;
- }
- $query->execute(array($uid_owner, $uid, $source, $target, $permissions));
// Update mtime of shared folder to invoke a file cache rescan
$rootView=new OC_FilesystemView('/');
if (!$rootView->is_dir($sharedFolder)) {
@@ -119,6 +115,10 @@ class OC_Share {
$rootView->mkdir($sharedFolder);
}
$rootView->touch($sharedFolder);
+ if (isset($gid)) {
+ $uid = $uid."@".$gid;
+ }
+ $query->execute(array($uid_owner, $uid, $source, $target, $permissions));
}
}
}
diff --git a/apps/gallery/appinfo/database.xml b/apps/gallery/appinfo/database.xml
index d1ccd6b5a24..1683e0ca2c7 100644
--- a/apps/gallery/appinfo/database.xml
+++ b/apps/gallery/appinfo/database.xml
@@ -3,7 +3,7 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
<name>*dbprefix*pictures_images_cache</name>
<declaration>
diff --git a/apps/gallery/lib/managers.php b/apps/gallery/lib/managers.php
index 9a2dbd3bae2..d4d978dbdd0 100644
--- a/apps/gallery/lib/managers.php
+++ b/apps/gallery/lib/managers.php
@@ -2,8 +2,6 @@
namespace OC\Pictures;
-require_once('lib/base.php');
-
class DatabaseManager {
private static $instance = null;
const TAG = 'DatabaseManager';
diff --git a/apps/media/appinfo/database.xml b/apps/media/appinfo/database.xml
index 702ae9c28b4..9b942d282bb 100644
--- a/apps/media/appinfo/database.xml
+++ b/apps/media/appinfo/database.xml
@@ -5,7 +5,7 @@
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
diff --git a/apps/remoteStorage/appinfo/database.xml b/apps/remoteStorage/appinfo/database.xml
index 00ee4942744..d48f9f747b1 100644
--- a/apps/remoteStorage/appinfo/database.xml
+++ b/apps/remoteStorage/appinfo/database.xml
@@ -3,7 +3,7 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
<name>*dbprefix*authtoken</name>
<declaration>
diff --git a/apps/tasks/js/tasks.js b/apps/tasks/js/tasks.js
index d1e3a9969b4..6547b80981c 100644
--- a/apps/tasks/js/tasks.js
+++ b/apps/tasks/js/tasks.js
@@ -310,8 +310,11 @@ OC.Tasks = {
};
$(document).ready(function(){
- fillHeight($('#tasks_lists'));
- fillWindow($('#tasks_list'));
+ $(window).resize(function () {
+ fillHeight($('#tasks_lists'));
+ fillWindow($('#tasks_list'));
+ });
+ $(window).trigger('resize');
/*-------------------------------------------------------------------------
* Actions for startup
diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php
index 6bed7bdd93f..bd2a3e897e5 100644
--- a/core/lostpassword/index.php
+++ b/core/lostpassword/index.php
@@ -17,7 +17,7 @@ if (isset($_POST['user'])) {
OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', $token);
$email = OC_Preferences::getValue($_POST['user'], 'settings', 'email', '');
if (!empty($email) and isset($_POST['sectoken']) and isset($_SESSION['sectoken']) and ($_POST['sectoken']==$_SESSION['sectoken']) ) {
- $link = OC_Helper::linkToAbsolute('core/lostpassword', 'resetpassword.php').'?user='.$_POST['user'].'&token='.$token;
+ $link = OC_Helper::linkToAbsolute('core/lostpassword', 'resetpassword.php').'?user='.urlencode($_POST['user']).'&token='.$token;
$tmpl = new OC_Template('core/lostpassword', 'email');
$tmpl->assign('link', $link);
$msg = $tmpl->fetchPage();
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index e04fcabf137..6f9b02237c9 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -30,6 +30,16 @@
echo '/>';
?>
<?php endforeach; ?>
+ <script type="text/javascript">
+ $(function() {
+ var requesttoken = '<?php echo $_['requesttoken']; ?>';
+ $(document).bind('ajaxSend', function(elm, xhr, s){
+ if(requesttoken) {
+ xhr.setRequestHeader('requesttoken', requesttoken);
+ }
+ });
+ });
+ </script>
</head>
<body id="<?php echo $_['bodyid'];?>">
diff --git a/lib/config.php b/lib/config.php
index e3a9c11f247..9279549b1bb 100644
--- a/lib/config.php
+++ b/lib/config.php
@@ -170,14 +170,18 @@ class OC_Config{
}
$content .= ");\n?>\n";
+ $filename = OC::$SERVERROOT."/config/config.php";
// Write the file
- $result=@file_put_contents( OC::$SERVERROOT."/config/config.php", $content );
+ $result=@file_put_contents( $filename, $content );
if(!$result) {
$tmpl = new OC_Template( '', 'error', 'guest' );
$tmpl->assign('errors',array(1=>array('error'=>"Can't write into config directory 'config'",'hint'=>"You can usually fix this by giving the webserver user write access to the config directory in owncloud")));
$tmpl->printPage();
exit;
}
+ // Prevent others not to read the config
+ @chmod($filename, 0640);
+
return true;
}
}
diff --git a/lib/filesystem.php b/lib/filesystem.php
index 454bb1aa81a..dd74daffa4e 100644
--- a/lib/filesystem.php
+++ b/lib/filesystem.php
@@ -319,6 +319,9 @@ class OC_Filesystem{
if(substr($mountpoint,-1)!=='/'){
$mountpoint=$mountpoint.'/';
}
+ if (self::getView() != null && $mountpoint != '/' && !self::is_dir(basename($mountpoint))) {
+ self::mkdir(basename($mountpoint));
+ }
self::$mounts[$mountpoint]=array('class'=>$class,'arguments'=>$arguments);
}
diff --git a/lib/json.php b/lib/json.php
index f3bbe9ac899..dfc0a7b894e 100644
--- a/lib/json.php
+++ b/lib/json.php
@@ -42,6 +42,18 @@ class OC_JSON{
}
/**
+ * @brief Check an ajax get/post call if the request token is valid.
+ * @return json Error msg if not valid.
+ */
+ public static function callCheck(){
+ if( !OC_Util::isCallRegistered()){
+ $l = OC_L10N::get('core');
+ self::error(array( 'data' => array( 'message' => $l->t('Token expired. Please reload page.') )));
+ exit();
+ }
+ }
+
+ /**
* Check if the user is a admin, send json error msg if not
*/
public static function checkAdminUser(){
diff --git a/lib/public/json.php b/lib/public/json.php
index a8554671d10..b6edbd65bd5 100644
--- a/lib/public/json.php
+++ b/lib/public/json.php
@@ -53,6 +53,13 @@ class JSON {
return(\OC_JSON::checkLoggedIn());
}
+ /**
+ * @brief Check an ajax get/post call if the request token is valid.
+ * @return json Error msg if not valid.
+ */
+ public static function callCheck(){
+ return(\OC_JSON::callCheck());
+ }
/**
* @brief Send json success msg
diff --git a/lib/template.php b/lib/template.php
index ba82b21774a..a3700e133e7 100644
--- a/lib/template.php
+++ b/lib/template.php
@@ -155,6 +155,9 @@ class OC_Template{
$this->renderas = $renderas;
$this->application = $app;
$this->vars = array();
+ if($renderas == 'user') {
+ $this->vars['requesttoken'] = OC_Util::callRegister();
+ }
$this->l10n = OC_L10N::get($app);
header('X-Frame-Options: Sameorigin');
header('X-XSS-Protection: 1; mode=block');
@@ -374,6 +377,7 @@ class OC_Template{
if( $this->renderas == "user" ){
$page = new OC_Template( "core", "layout.user" );
$page->assign('searchurl',OC_Helper::linkTo( 'search', 'index.php' ), false);
+ $page->assign('requesttoken', $this->vars['requesttoken']);
if(array_search(OC_APP::getCurrentApp(),array('settings','admin','help'))!==false){
$page->assign('bodyid','body-settings', false);
}else{
diff --git a/lib/util.php b/lib/util.php
index d1d5983dcfb..0266a8ecc5f 100755
--- a/lib/util.php
+++ b/lib/util.php
@@ -355,8 +355,9 @@ class OC_Util {
}
/**
- * Register an get/post call. This is important to prevent CSRF attacks
+ * @brief Register an get/post call. This is important to prevent CSRF attacks
* Todo: Write howto
+ * @return $token Generated token.
*/
public static function callRegister(){
//mamimum time before token exires
@@ -381,50 +382,48 @@ class OC_Util {
}
}
}
-
-
// return the token
return($token);
}
/**
- * Check an ajax get/post call if the request token is valid. exit if not.
- * Todo: Write howto
+ * @brief Check an ajax get/post call if the request token is valid.
+ * @return boolean False if request token is not set or is invalid.
*/
- public static function callCheck(){
+ public static function isCallRegistered(){
//mamimum time before token exires
$maxtime=(60*60); // 1 hour
-
- // searches in the get and post arrays for the token.
if(isset($_GET['requesttoken'])) {
$token=$_GET['requesttoken'];
}elseif(isset($_POST['requesttoken'])){
$token=$_POST['requesttoken'];
+ }elseif(isset($_SERVER['HTTP_REQUESTTOKEN'])){
+ $token=$_SERVER['HTTP_REQUESTTOKEN'];
}else{
- //no token found. exiting
- exit;
+ //no token found.
+ return false;
}
-
- // check if the token is in the user session and if the timestamp is from the last hour.
if(isset($_SESSION['requesttoken-'.$token])) {
$timestamp=$_SESSION['requesttoken-'.$token];
- if($timestamp+$maxtime<time){
- //token exired. exiting
- exit;
-
+ if($timestamp+$maxtime<time()){
+ return false;
}else{
//token valid
- return;
+ return true;
}
}else{
- //no token found. exiting
- exit;
+ return false;
}
}
-
-
-
-
+ /**
+ * @brief Check an ajax get/post call if the request token is valid. exit if not.
+ * Todo: Write howto
+ */
+ public static function callCheck(){
+ if(!OC_Util::isCallRegistered()) {
+ exit;
+ }
+ }
}
diff --git a/settings/css/settings.css b/settings/css/settings.css
index d4083857977..df1e3cfd3c2 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -34,8 +34,8 @@ li.selected { background-color:#ddd; }
#content>table:not(.nostyle) { margin-top:3em; }
table:not(.nostyle) { width:100%; }
#rightcontent { padding-left: 1em; }
-td.quota { position:relative; }
div.quota { float:right; display:block; position:absolute; right:25em; top:0; }
+div.quota-select-wrapper { position: relative; }
select.quota { position:absolute; left:0; top:0; width:10em; }
select.quota-user { position:relative; left:0; top:0; width:10em; }
input.quota-other { display:none; position:absolute; left:0.1em; top:0.1em; width:7em; border:none; -webkit-box-shadow: none -mox-box-shadow:none ; box-shadow:none; }
diff --git a/settings/js/users.js b/settings/js/users.js
index 7c3c3d37b18..f173357749e 100644
--- a/settings/js/users.js
+++ b/settings/js/users.js
@@ -95,9 +95,9 @@ $(document).ready(function(){
$(this).children('img').click();
});
- $('select.quota').live('change',function(){
+ $('select.quota, select.quota-user').live('change',function(){
var select=$(this);
- var uid=$(this).parent().parent().data('uid');
+ var uid=$(this).parent().parent().parent().data('uid');
var quota=$(this).val();
var other=$(this).next();
if(quota!='other'){
@@ -110,7 +110,7 @@ $(document).ready(function(){
other.focus();
}
});
- $('select.quota').each(function(i,select){
+ $('select.quota, select.quota-user').each(function(i,select){
$(select).data('previous',$(select).val());
})
@@ -207,9 +207,9 @@ $(document).ready(function(){
applyMultiplySelect(select);
$('#content table tbody').last().append(tr);
- tr.find('select.quota option').attr('selected',null);
- tr.find('select.quota option').first().attr('selected','selected');
- tr.find('select.quota').data('previous','default');
+ tr.find('select.quota-user option').attr('selected',null);
+ tr.find('select.quota-user option').first().attr('selected','selected');
+ tr.find('select.quota-user').data('previous','default');
}
}
);
diff --git a/settings/templates/users.php b/settings/templates/users.php
index 1951f17a0b7..ea3fe777ffe 100644
--- a/settings/templates/users.php
+++ b/settings/templates/users.php
@@ -12,29 +12,43 @@ foreach($_["groups"] as $group) {
<div id="controls">
<form id="newuser">
- <input id="newusername" placeholder="<?php echo $l->t('Name')?>" />
- <input type="password" id="newuserpassword" placeholder="<?php echo $l->t('Password')?>" />
- <select id="newusergroups" data-placeholder="groups" title="<?php echo $l->t('Groups')?>" multiple="multiple">
- <?php foreach($_["groups"] as $group): ?>
- <option value="<?php echo $group['name'];?>"><?php echo $group['name'];?></option>
- <?php endforeach;?>
- </select>
- <input type="submit" value="<?php echo $l->t('Create')?>" />
+ <input id="newusername" placeholder="<?php echo $l->t('Name')?>" /> <input
+ type="password" id="newuserpassword"
+ placeholder="<?php echo $l->t('Password')?>" /> <select
+ id="newusergroups" data-placeholder="groups"
+ title="<?php echo $l->t('Groups')?>" multiple="multiple">
+ <?php foreach($_["groups"] as $group): ?>
+ <option value="<?php echo $group['name'];?>">
+ <?php echo $group['name'];?>
+ </option>
+ <?php endforeach;?>
+ </select> <input type="submit" value="<?php echo $l->t('Create')?>" />
</form>
<div class="quota">
<span><?php echo $l->t('Default Quota');?>:</span>
- <select class='quota'>
- <?php foreach($_['quota_preset'] as $preset):?>
+ <div class="quota-select-wrapper">
+ <select class='quota'>
+ <?php foreach($_['quota_preset'] as $preset):?>
<?php if($preset!='default'):?>
- <option <?php if($_['default_quota']==$preset) echo 'selected="selected"';?> value='<?php echo $preset;?>'><?php echo $preset;?></option>
+ <option
+ <?php if($_['default_quota']==$preset) echo 'selected="selected"';?>
+ value='<?php echo $preset;?>'>
+ <?php echo $preset;?>
+ </option>
<?php endif;?>
- <?php endforeach;?>
- <?php if(array_search($_['default_quota'],$_['quota_preset'])===false):?>
- <option selected="selected" value='<?php echo $_['default_quota'];?>'><?php echo $_['default_quota'];?></option>
- <?php endif;?>
- <option value='other'><?php echo $l->t('Other');?>...</option>
- </select>
- <input class='quota-other'></input>
+ <?php endforeach;?>
+ <?php if(array_search($_['default_quota'],$_['quota_preset'])===false):?>
+ <option selected="selected"
+ value='<?php echo $_['default_quota'];?>'>
+ <?php echo $_['default_quota'];?>
+ </option>
+ <?php endif;?>
+ <option value='other'>
+ <?php echo $l->t('Other');?>
+ ...
+ </option>
+ </select> <input class='quota-other'></input>
+ </div>
</div>
</div>
@@ -49,38 +63,52 @@ foreach($_["groups"] as $group) {
</tr>
</thead>
<tbody>
- <?php foreach($_["users"] as $user): ?>
+ <?php foreach($_["users"] as $user): ?>
<tr data-uid="<?php echo $user["name"] ?>">
<td class="name"><?php echo $user["name"]; ?></td>
- <td class="password">
- <span>●●●●●●●</span>
- <img class="svg action" src="<?php echo image_path('core','actions/rename.svg')?>" alt="set new password" title="set new password" />
+ <td class="password"><span>●●●●●●●</span> <img class="svg action"
+ src="<?php echo image_path('core','actions/rename.svg')?>"
+ alt="set new password" title="set new password" />
</td>
- <td class="groups">
- <select data-username="<?php echo $user['name'] ;?>" data-user-groups="<?php echo $user['groups'] ;?>" data-placeholder="groups" title="<?php echo $l->t('Groups')?>" multiple="multiple">
+ <td class="groups"><select
+ data-username="<?php echo $user['name'] ;?>"
+ data-user-groups="<?php echo $user['groups'] ;?>"
+ data-placeholder="groups" title="<?php echo $l->t('Groups')?>"
+ multiple="multiple">
<?php foreach($_["groups"] as $group): ?>
- <option value="<?php echo $group['name'];?>"><?php echo $group['name'];?></option>
+ <option value="<?php echo $group['name'];?>">
+ <?php echo $group['name'];?>
+ </option>
<?php endforeach;?>
- </select>
+ </select>
</td>
<td class="quota">
- <select class='quota-user'>
- <?php foreach($_['quota_preset'] as $preset):?>
- <option <?php if($user['quota']==$preset) echo 'selected="selected"';?> value='<?php echo $preset;?>'><?php echo $preset;?></option>
- <?php endforeach;?>
- <?php if(array_search($user['quota'],$_['quota_preset'])===false):?>
- <option selected="selected" value='<?php echo $user['quota'];?>'><?php echo $user['quota'];?></option>
- <?php endif;?>
- <option value='other'><?php echo $l->t('Other');?>...</option>
- </select>
- <input class='quota-other'></input>
+ <div class="quota-select-wrapper">
+ <select class='quota-user'>
+ <?php foreach($_['quota_preset'] as $preset):?>
+ <option
+ <?php if($user['quota']==$preset) echo 'selected="selected"';?>
+ value='<?php echo $preset;?>'>
+ <?php echo $preset;?>
+ </option>
+ <?php endforeach;?>
+ <?php if(array_search($user['quota'],$_['quota_preset'])===false):?>
+ <option selected="selected" value='<?php echo $user['quota'];?>'>
+ <?php echo $user['quota'];?>
+ </option>
+ <?php endif;?>
+ <option value='other'>
+ <?php echo $l->t('Other');?>
+ ...
+ </option>
+ </select> <input class='quota-other'></input>
+ </div>
</td>
- <td class="remove">
- <?php if($user['name']!=OC_User::getUser()):?>
- <img alt="Delete" title="<?php echo $l->t('Delete')?>" class="svg action" src="<?php echo image_path('core','actions/delete.svg') ?>" />
- <?php endif;?>
+ <td class="remove"><?php if($user['name']!=OC_User::getUser()):?> <img
+ alt="Delete" title="<?php echo $l->t('Delete')?>" class="svg action"
+ src="<?php echo image_path('core','actions/delete.svg') ?>" /> <?php endif;?>
</td>
</tr>
- <?php endforeach; ?>
+ <?php endforeach; ?>
</tbody>
</table>