summaryrefslogtreecommitdiff
path: root/lib/epub.js
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2017-02-25 00:27:31 +0300
committerAndrew Dolgov <[email protected]>2017-02-25 00:27:31 +0300
commit885224d569d535b7e63ab301e4fd6cdc71c5c49b (patch)
tree30316ac1023460a53b9920e61f11f10fa87f56d0 /lib/epub.js
initial
Diffstat (limited to 'lib/epub.js')
-rw-r--r--lib/epub.js9077
1 files changed, 9077 insertions, 0 deletions
diff --git a/lib/epub.js b/lib/epub.js
new file mode 100644
index 0000000..c00249d
--- /dev/null
+++ b/lib/epub.js
@@ -0,0 +1,9077 @@
+/*!
+ * @overview RSVP - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2016 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+ * @license Licensed under MIT license
+ * See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE
+ * @version 3.3.3
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.RSVP = global.RSVP || {})));
+}(this, (function (exports) { 'use strict';
+
+function indexOf(callbacks, callback) {
+ for (var i = 0, l = callbacks.length; i < l; i++) {
+ if (callbacks[i] === callback) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function callbacksFor(object) {
+ var callbacks = object._promiseCallbacks;
+
+ if (!callbacks) {
+ callbacks = object._promiseCallbacks = {};
+ }
+
+ return callbacks;
+}
+
+/**
+ @class RSVP.EventTarget
+*/
+var EventTarget = {
+
+ /**
+ `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
+ Example:
+ ```javascript
+ let object = {};
+ RSVP.EventTarget.mixin(object);
+ object.on('finished', function(event) {
+ // handle event
+ });
+ object.trigger('finished', { detail: value });
+ ```
+ `EventTarget.mixin` also works with prototypes:
+ ```javascript
+ let Person = function() {};
+ RSVP.EventTarget.mixin(Person.prototype);
+ let yehuda = new Person();
+ let tom = new Person();
+ yehuda.on('poke', function(event) {
+ console.log('Yehuda says OW');
+ });
+ tom.on('poke', function(event) {
+ console.log('Tom says OW');
+ });
+ yehuda.trigger('poke');
+ tom.trigger('poke');
+ ```
+ @method mixin
+ @for RSVP.EventTarget
+ @private
+ @param {Object} object object to extend with EventTarget methods
+ */
+ mixin: function mixin(object) {
+ object['on'] = this['on'];
+ object['off'] = this['off'];
+ object['trigger'] = this['trigger'];
+ object._promiseCallbacks = undefined;
+ return object;
+ },
+
+ /**
+ Registers a callback to be executed when `eventName` is triggered
+ ```javascript
+ object.on('event', function(eventInfo){
+ // handle the event
+ });
+ object.trigger('event');
+ ```
+ @method on
+ @for RSVP.EventTarget
+ @private
+ @param {String} eventName name of the event to listen for
+ @param {Function} callback function to be called when the event is triggered.
+ */
+ on: function on(eventName, callback) {
+ if (typeof callback !== 'function') {
+ throw new TypeError('Callback must be a function');
+ }
+
+ var allCallbacks = callbacksFor(this),
+ callbacks = undefined;
+
+ callbacks = allCallbacks[eventName];
+
+ if (!callbacks) {
+ callbacks = allCallbacks[eventName] = [];
+ }
+
+ if (indexOf(callbacks, callback) === -1) {
+ callbacks.push(callback);
+ }
+ },
+
+ /**
+ You can use `off` to stop firing a particular callback for an event:
+ ```javascript
+ function doStuff() { // do stuff! }
+ object.on('stuff', doStuff);
+ object.trigger('stuff'); // doStuff will be called
+ // Unregister ONLY the doStuff callback
+ object.off('stuff', doStuff);
+ object.trigger('stuff'); // doStuff will NOT be called
+ ```
+ If you don't pass a `callback` argument to `off`, ALL callbacks for the
+ event will not be executed when the event fires. For example:
+ ```javascript
+ let callback1 = function(){};
+ let callback2 = function(){};
+ object.on('stuff', callback1);
+ object.on('stuff', callback2);
+ object.trigger('stuff'); // callback1 and callback2 will be executed.
+ object.off('stuff');
+ object.trigger('stuff'); // callback1 and callback2 will not be executed!
+ ```
+ @method off
+ @for RSVP.EventTarget
+ @private
+ @param {String} eventName event to stop listening to
+ @param {Function} callback optional argument. If given, only the function
+ given will be removed from the event's callback queue. If no `callback`
+ argument is given, all callbacks will be removed from the event's callback
+ queue.
+ */
+ off: function off(eventName, callback) {
+ var allCallbacks = callbacksFor(this),
+ callbacks = undefined,
+ index = undefined;
+
+ if (!callback) {
+ allCallbacks[eventName] = [];
+ return;
+ }
+
+ callbacks = allCallbacks[eventName];
+
+ index = indexOf(callbacks, callback);
+
+ if (index !== -1) {
+ callbacks.splice(index, 1);
+ }
+ },
+
+ /**
+ Use `trigger` to fire custom events. For example:
+ ```javascript
+ object.on('foo', function(){
+ console.log('foo event happened!');
+ });
+ object.trigger('foo');
+ // 'foo event happened!' logged to the console
+ ```
+ You can also pass a value as a second argument to `trigger` that will be
+ passed as an argument to all event listeners for the event:
+ ```javascript
+ object.on('foo', function(value){
+ console.log(value.name);
+ });
+ object.trigger('foo', { name: 'bar' });
+ // 'bar' logged to the console
+ ```
+ @method trigger
+ @for RSVP.EventTarget
+ @private
+ @param {String} eventName name of the event to be triggered
+ @param {*} options optional value to be passed to any event handlers for
+ the given `eventName`
+ */
+ trigger: function trigger(eventName, options, label) {
+ var allCallbacks = callbacksFor(this),
+ callbacks = undefined,
+ callback = undefined;
+
+ if (callbacks = allCallbacks[eventName]) {
+ // Don't cache the callbacks.length since it may grow
+ for (var i = 0; i < callbacks.length; i++) {
+ callback = callbacks[i];
+
+ callback(options, label);
+ }
+ }
+ }
+};
+
+var config = {
+ instrument: false
+};
+
+EventTarget['mixin'](config);
+
+function configure(name, value) {
+ if (name === 'onerror') {
+ // handle for legacy users that expect the actual
+ // error to be passed to their function added via
+ // `RSVP.configure('onerror', someFunctionHere);`
+ config['on']('error', value);
+ return;
+ }
+
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+}
+
+function objectOrFunction(x) {
+ return typeof x === 'function' || typeof x === 'object' && x !== null;
+}
+
+function isFunction(x) {
+ return typeof x === 'function';
+}
+
+function isMaybeThenable(x) {
+ return typeof x === 'object' && x !== null;
+}
+
+var _isArray = undefined;
+if (!Array.isArray) {
+ _isArray = function (x) {
+ return Object.prototype.toString.call(x) === '[object Array]';
+ };
+} else {
+ _isArray = Array.isArray;
+}
+
+var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+var now = Date.now || function () {
+ return new Date().getTime();
+};
+
+function F() {}
+
+var o_create = Object.create || function (o) {
+ if (arguments.length > 1) {
+ throw new Error('Second argument not supported');
+ }
+ if (typeof o !== 'object') {
+ throw new TypeError('Argument must be an object');
+ }
+ F.prototype = o;
+ return new F();
+};
+
+var queue = [];
+
+function scheduleFlush() {
+ setTimeout(function () {
+ for (var i = 0; i < queue.length; i++) {
+ var entry = queue[i];
+
+ var payload = entry.payload;
+
+ payload.guid = payload.key + payload.id;
+ payload.childGuid = payload.key + payload.childId;
+ if (payload.error) {
+ payload.stack = payload.error.stack;
+ }
+
+ config['trigger'](entry.name, entry.payload);
+ }
+ queue.length = 0;
+ }, 50);
+}
+function instrument(eventName, promise, child) {
+ if (1 === queue.push({
+ name: eventName,
+ payload: {
+ key: promise._guidKey,
+ id: promise._id,
+ eventName: eventName,
+ detail: promise._result,
+ childId: child && child._id,
+ label: promise._label,
+ timeStamp: now(),
+ error: config["instrument-with-stack"] ? new Error(promise._label) : null
+ } })) {
+ scheduleFlush();
+ }
+}
+
+/**
+ `RSVP.Promise.resolve` returns a promise that will become resolved with the
+ passed `value`. It is shorthand for the following:
+
+ ```javascript
+ let promise = new RSVP.Promise(function(resolve, reject){
+ resolve(1);
+ });
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ let promise = RSVP.Promise.resolve(1);
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ @method resolve
+ @static
+ @param {*} object value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+*/
+function resolve$1(object, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (object && typeof object === 'object' && object.constructor === Constructor) {
+ return object;
+ }
+
+ var promise = new Constructor(noop, label);
+ resolve(promise, object);
+ return promise;
+}
+
+function withOwnPromise() {
+ return new TypeError('A promises callback cannot return that same promise.');
+}
+
+function noop() {}
+
+var PENDING = void 0;
+var FULFILLED = 1;
+var REJECTED = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function getThen(promise) {
+ try {
+ return promise.then;
+ } catch (error) {
+ GET_THEN_ERROR.error = error;
+ return GET_THEN_ERROR;
+ }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+ try {
+ then.call(value, fulfillmentHandler, rejectionHandler);
+ } catch (e) {
+ return e;
+ }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+ config.async(function (promise) {
+ var sealed = false;
+ var error = tryThen(then, thenable, function (value) {
+ if (sealed) {
+ return;
+ }
+ sealed = true;
+ if (thenable !== value) {
+ resolve(promise, value, undefined);
+ } else {
+ fulfill(promise, value);
+ }
+ }, function (reason) {
+ if (sealed) {
+ return;
+ }
+ sealed = true;
+
+ reject(promise, reason);
+ }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+ if (!sealed && error) {
+ sealed = true;
+ reject(promise, error);
+ }
+ }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+ if (thenable._state === FULFILLED) {
+ fulfill(promise, thenable._result);
+ } else if (thenable._state === REJECTED) {
+ thenable._onError = null;
+ reject(promise, thenable._result);
+ } else {
+ subscribe(thenable, undefined, function (value) {
+ if (thenable !== value) {
+ resolve(promise, value, undefined);
+ } else {
+ fulfill(promise, value);
+ }
+ }, function (reason) {
+ return reject(promise, reason);
+ });
+ }
+}
+
+function handleMaybeThenable(promise, maybeThenable, then$$) {
+ if (maybeThenable.constructor === promise.constructor && then$$ === then && promise.constructor.resolve === resolve$1) {
+ handleOwnThenable(promise, maybeThenable);
+ } else {
+ if (then$$ === GET_THEN_ERROR) {
+ reject(promise, GET_THEN_ERROR.error);
+ } else if (then$$ === undefined) {
+ fulfill(promise, maybeThenable);
+ } else if (isFunction(then$$)) {
+ handleForeignThenable(promise, maybeThenable, then$$);
+ } else {
+ fulfill(promise, maybeThenable);
+ }
+ }
+}
+
+function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (objectOrFunction(value)) {
+ handleMaybeThenable(promise, value, getThen(value));
+ } else {
+ fulfill(promise, value);
+ }
+}
+
+function publishRejection(promise) {
+ if (promise._onError) {
+ promise._onError(promise._result);
+ }
+
+ publish(promise);
+}
+
+function fulfill(promise, value) {
+ if (promise._state !== PENDING) {
+ return;
+ }
+
+ promise._result = value;
+ promise._state = FULFILLED;
+
+ if (promise._subscribers.length === 0) {
+ if (config.instrument) {
+ instrument('fulfilled', promise);
+ }
+ } else {
+ config.async(publish, promise);
+ }
+}
+
+function reject(promise, reason) {
+ if (promise._state !== PENDING) {
+ return;
+ }
+ promise._state = REJECTED;
+ promise._result = reason;
+ config.async(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ parent._onError = null;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+
+ if (length === 0 && parent._state) {
+ config.async(publish, parent);
+ }
+}
+
+function publish(promise) {
+ var subscribers = promise._subscribers;
+ var settled = promise._state;
+
+ if (config.instrument) {
+ instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
+ }
+
+ if (subscribers.length === 0) {
+ return;
+ }
+
+ var child = undefined,
+ callback = undefined,
+ detail = promise._result;
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ if (child) {
+ invokeCallback(settled, child, callback, detail);
+ } else {
+ callback(detail);
+ }
+ }
+
+ promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+ this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+ try {
+ return callback(detail);
+ } catch (e) {
+ TRY_CATCH_ERROR.error = e;
+ return TRY_CATCH_ERROR;
+ }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value = undefined,
+ error = undefined,
+ succeeded = undefined,
+ failed = undefined;
+
+ if (hasCallback) {
+ value = tryCatch(callback, detail);
+
+ if (value === TRY_CATCH_ERROR) {
+ failed = true;
+ error = value.error;
+ value = null;
+ } else {
+ succeeded = true;
+ }
+
+ if (promise === value) {
+ reject(promise, withOwnPromise());
+ return;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (promise._state !== PENDING) {
+ // noop
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ fulfill(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+}
+
+function initializePromise(promise, resolver) {
+ var resolved = false;
+ try {
+ resolver(function (value) {
+ if (resolved) {
+ return;
+ }
+ resolved = true;
+ resolve(promise, value);
+ }, function (reason) {
+ if (resolved) {
+ return;
+ }
+ resolved = true;
+ reject(promise, reason);
+ });
+ } catch (e) {
+ reject(promise, e);
+ }
+}
+
+function then(onFulfillment, onRejection, label) {
+ var _arguments = arguments;
+
+ var parent = this;
+ var state = parent._state;
+
+ if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+ config.instrument && instrument('chained', parent, parent);
+ return parent;
+ }
+
+ parent._onError = null;
+
+ var child = new parent.constructor(noop, label);
+ var result = parent._result;
+
+ config.instrument && instrument('chained', parent, child);
+
+ if (state) {
+ (function () {
+ var callback = _arguments[state - 1];
+ config.async(function () {
+ return invokeCallback(state, child, callback, result);
+ });
+ })();
+ } else {
+ subscribe(parent, child, onFulfillment, onRejection);
+ }
+
+ return child;
+}
+
+function makeSettledResult(state, position, value) {
+ if (state === FULFILLED) {
+ return {
+ state: 'fulfilled',
+ value: value
+ };
+ } else {
+ return {
+ state: 'rejected',
+ reason: value
+ };
+ }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+ this._instanceConstructor = Constructor;
+ this.promise = new Constructor(noop, label);
+ this._abortOnReject = abortOnReject;
+
+ if (this._validateInput(input)) {
+ this._input = input;
+ this.length = input.length;
+ this._remaining = input.length;
+
+ this._init();
+
+ if (this.length === 0) {
+ fulfill(this.promise, this._result);
+ } else {
+ this.length = this.length || 0;
+ this._enumerate();
+ if (this._remaining === 0) {
+ fulfill(this.promise, this._result);
+ }
+ }
+ } else {
+ reject(this.promise, this._validationError());
+ }
+}
+
+Enumerator.prototype._validateInput = function (input) {
+ return isArray(input);
+};
+
+Enumerator.prototype._validationError = function () {
+ return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function () {
+ this._result = new Array(this.length);
+};
+
+Enumerator.prototype._enumerate = function () {
+ var length = this.length;
+ var promise = this.promise;
+ var input = this._input;
+
+ for (var i = 0; promise._state === PENDING && i < length; i++) {
+ this._eachEntry(input[i], i);
+ }
+};
+
+Enumerator.prototype._settleMaybeThenable = function (entry, i) {
+ var c = this._instanceConstructor;
+ var resolve = c.resolve;
+
+ if (resolve === resolve$1) {
+ var then$$ = getThen(entry);
+
+ if (then$$ === then && entry._state !== PENDING) {
+ entry._onError = null;
+ this._settledAt(entry._state, i, entry._result);
+ } else if (typeof then$$ !== 'function') {
+ this._remaining--;
+ this._result[i] = this._makeResult(FULFILLED, i, entry);
+ } else if (c === Promise) {
+ var promise = new c(noop);
+ handleMaybeThenable(promise, entry, then$$);
+ this._willSettleAt(promise, i);
+ } else {
+ this._willSettleAt(new c(function (resolve) {
+ return resolve(entry);
+ }), i);
+ }
+ } else {
+ this._willSettleAt(resolve(entry), i);
+ }
+};
+
+Enumerator.prototype._eachEntry = function (entry, i) {
+ if (isMaybeThenable(entry)) {
+ this._settleMaybeThenable(entry, i);
+ } else {
+ this._remaining--;
+ this._result[i] = this._makeResult(FULFILLED, i, entry);
+ }
+};
+
+Enumerator.prototype._settledAt = function (state, i, value) {
+ var promise = this.promise;
+
+ if (promise._state === PENDING) {
+ this._remaining--;
+
+ if (this._abortOnReject && state === REJECTED) {
+ reject(promise, value);
+ } else {
+ this._result[i] = this._makeResult(state, i, value);
+ }
+ }
+
+ if (this._remaining === 0) {
+ fulfill(promise, this._result);
+ }
+};
+
+Enumerator.prototype._makeResult = function (state, i, value) {
+ return value;
+};
+
+Enumerator.prototype._willSettleAt = function (promise, i) {
+ var enumerator = this;
+
+ subscribe(promise, undefined, function (value) {
+ return enumerator._settledAt(FULFILLED, i, value);
+ }, function (reason) {
+ return enumerator._settledAt(REJECTED, i, reason);
+ });
+};
+
+/**
+ `RSVP.Promise.all` accepts an array of promises, and returns a new promise which
+ is fulfilled with an array of fulfillment values for the passed promises, or
+ rejected with the reason of the first passed promise to be rejected. It casts all
+ elements of the passed iterable to promises as it runs this algorithm.
+
+ Example:
+
+ ```javascript
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.resolve(2);
+ let promise3 = RSVP.resolve(3);
+ let promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.reject(new Error("2"));
+ let promise3 = RSVP.reject(new Error("3"));
+ let promises = [ promise1, promise2, promise3 ];
+
+ RSVP.Promise.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @static
+ @param {Array} entries array of promises
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ @static
+*/
+function all(entries, label) {
+ return new Enumerator(this, entries, true, /* abort on reject */label).promise;
+}
+
+/**
+ `RSVP.Promise.race` returns a new promise which is settled in the same way as the
+ first passed promise to settle.
+
+ Example:
+
+ ```javascript
+ let promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 1');
+ }, 200);
+ });
+
+ let promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 2');
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // result === 'promise 2' because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.Promise.race` is deterministic in that only the state of the first
+ settled promise matters. For example, even if other promises given to the
+ `promises` array argument are resolved, but the first settled promise has
+ become rejected before the other promises became fulfilled, the returned
+ promise will become rejected:
+
+ ```javascript
+ let promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 1');
+ }, 200);
+ });
+
+ let promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error('promise 2'));
+ }, 100);
+ });
+
+ RSVP.Promise.race([promise1, promise2]).then(function(result){
+ // Code here never runs
+ }, function(reason){
+ // reason.message === 'promise 2' because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ An example real-world use case is implementing timeouts:
+
+ ```javascript
+ RSVP.Promise.race([ajax('foo.json'), timeout(5000)])
+ ```
+
+ @method race
+ @static
+ @param {Array} entries array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise which settles in the same way as the first passed
+ promise to settle.
+*/
+function race(entries, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ var promise = new Constructor(noop, label);
+
+ if (!isArray(entries)) {
+ reject(promise, new TypeError('You must pass an array to race.'));
+ return promise;
+ }
+
+ for (var i = 0; promise._state === PENDING && i < entries.length; i++) {
+ subscribe(Constructor.resolve(entries[i]), undefined, function (value) {
+ return resolve(promise, value);
+ }, function (reason) {
+ return reject(promise, reason);
+ });
+ }
+
+ return promise;
+}
+
+/**
+ `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
+ It is shorthand for the following:
+
+ ```javascript
+ let promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ let promise = RSVP.Promise.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @static
+ @param {*} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+*/
+function reject$1(reason, label) {
+ /*jshint validthis:true */
+ var Constructor = this;
+ var promise = new Constructor(noop, label);
+ reject(promise, reason);
+ return promise;
+}
+
+var guidKey = 'rsvp_' + now() + '-';
+var counter = 0;
+
+function needsResolver() {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+/**
+ Promise objects represent the eventual result of an asynchronous operation. The
+ primary way of interacting with a promise is through its `then` method, which
+ registers callbacks to receive either a promise’s eventual value or the reason
+ why the promise cannot be fulfilled.
+
+ Terminology
+ -----------
+
+ - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+ - `thenable` is an object or function that defines a `then` method.
+ - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+ - `exception` is a value that is thrown using the throw statement.
+ - `reason` is a value that indicates why a promise was rejected.
+ - `settled` the final resting state of a promise, fulfilled or rejected.
+
+ A promise can be in one of three states: pending, fulfilled, or rejected.
+
+ Promises that are fulfilled have a fulfillment value and are in the fulfilled
+ state. Promises that are rejected have a rejection reason and are in the
+ rejected state. A fulfillment value is never a thenable.
+
+ Promises can also be said to *resolve* a value. If this value is also a
+ promise, then the original promise's settled state will match the value's
+ settled state. So a promise that *resolves* a promise that rejects will
+ itself reject, and a promise that *resolves* a promise that fulfills will
+ itself fulfill.
+
+
+ Basic Usage:
+ ------------
+
+ ```js
+ let promise = new Promise(function(resolve, reject) {
+ // on success
+ resolve(value);
+
+ // on failure
+ reject(reason);
+ });
+
+ promise.then(function(value) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Advanced Usage:
+ ---------------
+
+ Promises shine when abstracting away asynchronous interactions such as
+ `XMLHttpRequest`s.
+
+ ```js
+ function getJSON(url) {
+ return new Promise(function(resolve, reject){
+ let xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url);
+ xhr.onreadystatechange = handler;
+ xhr.responseType = 'json';
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.send();
+
+ function handler() {
+ if (this.readyState === this.DONE) {
+ if (this.status === 200) {
+ resolve(this.response);
+ } else {
+ reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+ }
+ }
+ };
+ });
+ }
+
+ getJSON('/posts.json').then(function(json) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Unlike callbacks, promises are great composable primitives.
+
+ ```js
+ Promise.all([
+ getJSON('/posts'),
+ getJSON('/comments')
+ ]).then(function(values){
+ values[0] // => postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class RSVP.Promise
+ @param {function} resolver
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @constructor
+*/
+function Promise(resolver, label) {
+ this._id = counter++;
+ this._label = label;
+ this._state = undefined;
+ this._result = undefined;
+ this._subscribers = [];
+
+ config.instrument && instrument('created', this);
+
+ if (noop !== resolver) {
+ typeof resolver !== 'function' && needsResolver();
+ this instanceof Promise ? initializePromise(this, resolver) : needsNew();
+ }
+}
+
+Promise.cast = resolve$1; // deprecated
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = resolve$1;
+Promise.reject = reject$1;
+
+Promise.prototype = {
+ constructor: Promise,
+
+ _guidKey: guidKey,
+
+ _onError: function _onError(reason) {
+ var promise = this;
+ config.after(function () {
+ if (promise._onError) {
+ config['trigger']('error', reason, promise._label);
+ }
+ });
+ },
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+
+ Chaining
+ --------
+
+ The return value of `then` is itself a promise. This second, 'downstream'
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return 'default name';
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `'default name'`
+ });
+
+ findUser().then(function (user) {
+ throw new Error('Found user, but still unhappy');
+ }, function (reason) {
+ throw new Error('`findUser` rejected and we\'re unhappy');
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+ // If `findUser` rejected, `reason` will be '`findUser` rejected and we\'re unhappy'.
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException('Upstream error');
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+
+ Assimilation
+ ------------
+
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+
+ If the assimliated promise rejects, then the downstream promise will also reject.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+
+ Simple Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ let result;
+
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+
+ Advanced Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ let author, books;
+
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+
+ function foundBooks(books) {
+
+ }
+
+ function failure(reason) {
+
+ }
+
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method then
+ @param {Function} onFulfillment
+ @param {Function} onRejection
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ then: then,
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+
+ ```js
+ function findAuthor(){
+ throw new Error('couldn\'t find that author');
+ }
+
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method catch
+ @param {Function} onRejection
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'catch': function _catch(onRejection, label) {
+ return this.then(undefined, onRejection, label);
+ },
+
+ /**
+ `finally` will be invoked regardless of the promise's fate just as native
+ try/catch/finally behaves
+
+ Synchronous example:
+
+ ```js
+ findAuthor() {
+ if (Math.random() > 0.5) {
+ throw new Error();
+ }
+ return new Author();
+ }
+
+ try {
+ return findAuthor(); // succeed or fail
+ } catch(error) {
+ return findOtherAuther();
+ } finally {
+ // always runs
+ // doesn't affect the return value
+ }
+ ```
+
+ Asynchronous example:
+
+ ```js
+ findAuthor().catch(function(reason){
+ return findOtherAuther();
+ }).finally(function(){
+ // author was either found, or not
+ });
+ ```
+
+ @method finally
+ @param {Function} callback
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'finally': function _finally(callback, label) {
+ var promise = this;
+ var constructor = promise.constructor;
+
+ return promise.then(function (value) {
+ return constructor.resolve(callback()).then(function () {
+ return value;
+ });
+ }, function (reason) {
+ return constructor.resolve(callback()).then(function () {
+ throw reason;
+ });
+ }, label);
+ }
+};
+
+function Result() {
+ this.value = undefined;
+}
+
+var ERROR = new Result();
+var GET_THEN_ERROR$1 = new Result();
+
+function getThen$1(obj) {
+ try {
+ return obj.then;
+ } catch (error) {
+ ERROR.value = error;
+ return ERROR;
+ }
+}
+
+function tryApply(f, s, a) {
+ try {
+ f.apply(s, a);
+ } catch (error) {
+ ERROR.value = error;
+ return ERROR;
+ }
+}
+
+function makeObject(_, argumentNames) {
+ var obj = {};
+ var length = _.length;
+ var args = new Array(length);
+
+ for (var x = 0; x < length; x++) {
+ args[x] = _[x];
+ }
+
+ for (var i = 0; i < argumentNames.length; i++) {
+ var _name = argumentNames[i];
+ obj[_name] = args[i + 1];
+ }
+
+ return obj;
+}
+
+function arrayResult(_) {
+ var length = _.length;
+ var args = new Array(length - 1);
+
+ for (var i = 1; i < length; i++) {
+ args[i - 1] = _[i];
+ }
+
+ return args;
+}
+
+function wrapThenable(_then, promise) {
+ return {
+ then: function then(onFulFillment, onRejection) {
+ return _then.call(promise, onFulFillment, onRejection);
+ }
+ };
+}
+
+/**
+ `RSVP.denodeify` takes a 'node-style' function and returns a function that
+ will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
+ browser when you'd prefer to use promises over using callbacks. For example,
+ `denodeify` transforms the following:
+
+ ```javascript
+ let fs = require('fs');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) return handleError(err);
+ handleData(data);
+ });
+ ```
+
+ into:
+
+ ```javascript
+ let fs = require('fs');
+ let readFile = RSVP.denodeify(fs.readFile);
+
+ readFile('myfile.txt').then(handleData, handleError);
+ ```
+
+ If the node function has multiple success parameters, then `denodeify`
+ just returns the first one:
+
+ ```javascript
+ let request = RSVP.denodeify(require('request'));
+
+ request('http://example.com').then(function(res) {
+ // ...
+ });
+ ```
+
+ However, if you need all success parameters, setting `denodeify`'s
+ second parameter to `true` causes it to return all success parameters
+ as an array:
+
+ ```javascript
+ let request = RSVP.denodeify(require('request'), true);
+
+ request('http://example.com').then(function(result) {
+ // result[0] -> res
+ // result[1] -> body
+ });
+ ```
+
+ Or if you pass it an array with names it returns the parameters as a hash:
+
+ ```javascript
+ let request = RSVP.denodeify(require('request'), ['res', 'body']);
+
+ request('http://example.com').then(function(result) {
+ // result.res
+ // result.body
+ });
+ ```
+
+ Sometimes you need to retain the `this`:
+
+ ```javascript
+ let app = require('express')();
+ let render = RSVP.denodeify(app.render.bind(app));
+ ```
+
+ The denodified function inherits from the original function. It works in all
+ environments, except IE 10 and below. Consequently all properties of the original
+ function are available to you. However, any properties you change on the
+ denodeified function won't be changed on the original function. Example:
+
+ ```javascript
+ let request = RSVP.denodeify(require('request')),
+ cookieJar = request.jar(); // <- Inheritance is used here
+
+ request('http://example.com', {jar: cookieJar}).then(function(res) {
+ // cookieJar.cookies holds now the cookies returned by example.com
+ });
+ ```
+
+ Using `denodeify` makes it easier to compose asynchronous operations instead
+ of using callbacks. For example, instead of:
+
+ ```javascript
+ let fs = require('fs');
+
+ fs.readFile('myfile.txt', function(err, data){
+ if (err) { ... } // Handle error
+ fs.writeFile('myfile2.txt', data, function(err){
+ if (err) { ... } // Handle error
+ console.log('done')
+ });
+ });
+ ```
+
+ you can chain the operations together using `then` from the returned promise:
+
+ ```javascript
+ let fs = require('fs');
+ let readFile = RSVP.denodeify(fs.readFile);
+ let writeFile = RSVP.denodeify(fs.writeFile);
+
+ readFile('myfile.txt').then(function(data){
+ return writeFile('myfile2.txt', data);
+ }).then(function(){
+ console.log('done')
+ }).catch(function(error){
+ // Handle error
+ });
+ ```
+
+ @method denodeify
+ @static
+ @for RSVP
+ @param {Function} nodeFunc a 'node-style' function that takes a callback as
+ its last argument. The callback expects an error to be passed as its first
+ argument (if an error occurred, otherwise null), and the value from the
+ operation as its second argument ('function(err, value){ }').
+ @param {Boolean|Array} [options] An optional paramter that if set
+ to `true` causes the promise to fulfill with the callback's success arguments
+ as an array. This is useful if the node function has multiple success
+ paramters. If you set this paramter to an array with names, the promise will
+ fulfill with a hash with these names as keys and the success parameters as
+ values.
+ @return {Function} a function that wraps `nodeFunc` to return an
+ `RSVP.Promise`
+ @static
+*/
+function denodeify(nodeFunc, options) {
+ var fn = function fn() {
+ var self = this;
+ var l = arguments.length;
+ var args = new Array(l + 1);
+ var promiseInput = false;
+
+ for (var i = 0; i < l; ++i) {
+ var arg = arguments[i];
+
+ if (!promiseInput) {
+ // TODO: clean this up
+ promiseInput = needsPromiseInput(arg);
+ if (promiseInput === GET_THEN_ERROR$1) {
+ var p = new Promise(noop);
+ reject(p, GET_THEN_ERROR$1.value);
+ return p;
+ } else if (promiseInput && promiseInput !== true) {
+ arg = wrapThenable(promiseInput, arg);
+ }
+ }
+ args[i] = arg;
+ }
+
+ var promise = new Promise(noop);
+
+ args[l] = function (err, val) {
+ if (err) reject(promise, err);else if (options === undefined) resolve(promise, val);else if (options === true) resolve(promise, arrayResult(arguments));else if (isArray(options)) resolve(promise, makeObject(arguments, options));else resolve(promise, val);
+ };
+
+ if (promiseInput) {
+ return handlePromiseInput(promise, args, nodeFunc, self);
+ } else {
+ return handleValueInput(promise, args, nodeFunc, self);
+ }
+ };
+
+ fn.__proto__ = nodeFunc;
+
+ return fn;
+}
+
+function handleValueInput(promise, args, nodeFunc, self) {
+ var result = tryApply(nodeFunc, self, args);
+ if (result === ERROR) {
+ reject(promise, result.value);
+ }
+ return promise;
+}
+
+function handlePromiseInput(promise, args, nodeFunc, self) {
+ return Promise.all(args).then(function (args) {
+ var result = tryApply(nodeFunc, self, args);
+ if (result === ERROR) {
+ reject(promise, result.value);
+ }
+ return promise;
+ });
+}
+
+function needsPromiseInput(arg) {
+ if (arg && typeof arg === 'object') {
+ if (arg.constructor === Promise) {
+ return true;
+ } else {
+ return getThen$1(arg);
+ }
+ } else {
+ return false;
+ }
+}
+
+/**
+ This is a convenient alias for `RSVP.Promise.all`.
+
+ @method all
+ @static
+ @for RSVP
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+*/
+function all$1(array, label) {
+ return Promise.all(array, label);
+}
+
+function AllSettled(Constructor, entries, label) {
+ this._superConstructor(Constructor, entries, false, /* don't abort on reject */label);
+}
+
+AllSettled.prototype = o_create(Enumerator.prototype);
+AllSettled.prototype._superConstructor = Enumerator;
+AllSettled.prototype._makeResult = makeSettledResult;
+AllSettled.prototype._validationError = function () {
+ return new Error('allSettled must be called with an array');
+};
+
+/**
+ `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
+ a fail-fast method, it waits until all the promises have returned and
+ shows you all the results. This is useful if you want to handle multiple
+ promises' failure states together as a set.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ settled. The return promise is fulfilled with an array of the states of
+ the promises passed into the `promises` array argument.
+
+ Each state object will either indicate fulfillment or rejection, and
+ provide the corresponding value or reason. The states will take one of
+ the following formats:
+
+ ```javascript
+ { state: 'fulfilled', value: value }
+ or
+ { state: 'rejected', reason: reason }
+ ```
+
+ Example:
+
+ ```javascript
+ let promise1 = RSVP.Promise.resolve(1);
+ let promise2 = RSVP.Promise.reject(new Error('2'));
+ let promise3 = RSVP.Promise.reject(new Error('3'));
+ let promises = [ promise1, promise2, promise3 ];
+
+ RSVP.allSettled(promises).then(function(array){
+ // array == [
+ // { state: 'fulfilled', value: 1 },
+ // { state: 'rejected', reason: Error },
+ // { state: 'rejected', reason: Error }
+ // ]
+ // Note that for the second item, reason.message will be '2', and for the
+ // third item, reason.message will be '3'.
+ }, function(error) {
+ // Not run. (This block would only be called if allSettled had failed,
+ // for instance if passed an incorrect argument type.)
+ });
+ ```
+
+ @method allSettled
+ @static
+ @for RSVP
+ @param {Array} entries
+ @param {String} label - optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with an array of the settled
+ states of the constituent promises.
+*/
+function allSettled(entries, label) {
+ return new AllSettled(Promise, entries, label).promise;
+}
+
+/**
+ This is a convenient alias for `RSVP.Promise.race`.
+
+ @method race
+ @static
+ @for RSVP
+ @param {Array} array Array of promises.
+ @param {String} label An optional label. This is useful
+ for tooling.
+ */
+function race$1(array, label) {
+ return Promise.race(array, label);
+}
+
+function PromiseHash(Constructor, object, label) {
+ this._superConstructor(Constructor, object, true, label);
+}
+
+PromiseHash.prototype = o_create(Enumerator.prototype);
+PromiseHash.prototype._superConstructor = Enumerator;
+PromiseHash.prototype._init = function () {
+ this._result = {};
+};
+
+PromiseHash.prototype._validateInput = function (input) {
+ return input && typeof input === 'object';
+};
+
+PromiseHash.prototype._validationError = function () {
+ return new Error('Promise.hash must be called with an object');
+};
+
+PromiseHash.prototype._enumerate = function () {
+ var enumerator = this;
+ var promise = enumerator.promise;
+ var input = enumerator._input;
+ var results = [];
+
+ for (var key in input) {
+ if (promise._state === PENDING && Object.prototype.hasOwnProperty.call(input, key)) {
+ results.push({
+ position: key,
+ entry: input[key]
+ });
+ }
+ }
+
+ var length = results.length;
+ enumerator._remaining = length;
+ var result = undefined;
+
+ for (var i = 0; promise._state === PENDING && i < length; i++) {
+ result = results[i];
+ enumerator._eachEntry(result.entry, result.position);
+ }
+};
+
+/**
+ `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
+ for its `promises` argument.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ fulfilled, or rejected if any of them become rejected. The returned promise
+ is fulfilled with a hash that has the same key names as the `promises` object
+ argument. If any of the values in the object are not promises, they will
+ simply be copied over to the fulfilled object.
+
+ Example:
+
+ ```javascript
+ let promises = {
+ myPromise: RSVP.resolve(1),
+ yourPromise: RSVP.resolve(2),
+ theirPromise: RSVP.resolve(3),
+ notAPromise: 4
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // hash here is an object that looks like:
+ // {
+ // myPromise: 1,
+ // yourPromise: 2,
+ // theirPromise: 3,
+ // notAPromise: 4
+ // }
+ });
+ ````
+
+ If any of the `promises` given to `RSVP.hash` are rejected, the first promise
+ that is rejected will be given as the reason to the rejection handler.
+
+ Example:
+
+ ```javascript
+ let promises = {
+ myPromise: RSVP.resolve(1),
+ rejectedPromise: RSVP.reject(new Error('rejectedPromise')),
+ anotherRejectedPromise: RSVP.reject(new Error('anotherRejectedPromise')),
+ };
+
+ RSVP.hash(promises).then(function(hash){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === 'rejectedPromise'
+ });
+ ```
+
+ An important note: `RSVP.hash` is intended for plain JavaScript objects that
+ are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
+ chains.
+
+ Example:
+
+ ```javascript
+ function MyConstructor(){
+ this.example = RSVP.resolve('Example');
+ }
+
+ MyConstructor.prototype = {
+ protoProperty: RSVP.resolve('Proto Property')
+ };
+
+ let myObject = new MyConstructor();
+
+ RSVP.hash(myObject).then(function(hash){
+ // protoProperty will not be present, instead you will just have an
+ // object that looks like:
+ // {
+ // example: 'Example'
+ // }
+ //
+ // hash.hasOwnProperty('protoProperty'); // false
+ // 'undefined' === typeof hash.protoProperty
+ });
+ ```
+
+ @method hash
+ @static
+ @for RSVP
+ @param {Object} object
+ @param {String} label optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all properties of `promises`
+ have been fulfilled, or rejected if any of them become rejected.
+*/
+function hash(object, label) {
+ return new PromiseHash(Promise, object, label).promise;
+}
+
+function HashSettled(Constructor, object, label) {
+ this._superConstructor(Constructor, object, false, label);
+}
+
+HashSettled.prototype = o_create(PromiseHash.prototype);
+HashSettled.prototype._superConstructor = Enumerator;
+HashSettled.prototype._makeResult = makeSettledResult;
+
+HashSettled.prototype._validationError = function () {
+ return new Error('hashSettled must be called with an object');
+};
+
+/**
+ `RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object
+ instead of an array for its `promises` argument.
+
+ Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method,
+ but like `RSVP.allSettled`, `hashSettled` waits until all the
+ constituent promises have returned and then shows you all the results
+ with their states and values/reasons. This is useful if you want to
+ handle multiple promises' failure states together as a set.
+
+ Returns a promise that is fulfilled when all the given promises have been
+ settled, or rejected if the passed parameters are invalid.
+
+ The returned promise is fulfilled with a hash that has the same key names as
+ the `promises` object argument. If any of the values in the object are not
+ promises, they will be copied over to the fulfilled object and marked with state
+ 'fulfilled'.
+
+ Example:
+
+ ```javascript
+ let promises = {
+ myPromise: RSVP.Promise.resolve(1),
+ yourPromise: RSVP.Promise.resolve(2),
+ theirPromise: RSVP.Promise.resolve(3),
+ notAPromise: 4
+ };
+
+ RSVP.hashSettled(promises).then(function(hash){
+ // hash here is an object that looks like:
+ // {
+ // myPromise: { state: 'fulfilled', value: 1 },
+ // yourPromise: { state: 'fulfilled', value: 2 },
+ // theirPromise: { state: 'fulfilled', value: 3 },
+ // notAPromise: { state: 'fulfilled', value: 4 }
+ // }
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.hash` are rejected, the state will
+ be set to 'rejected' and the reason for rejection provided.
+
+ Example:
+
+ ```javascript
+ let promises = {
+ myPromise: RSVP.Promise.resolve(1),
+ rejectedPromise: RSVP.Promise.reject(new Error('rejection')),
+ anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')),
+ };
+
+ RSVP.hashSettled(promises).then(function(hash){
+ // hash here is an object that looks like:
+ // {
+ // myPromise: { state: 'fulfilled', value: 1 },
+ // rejectedPromise: { state: 'rejected', reason: Error },
+ // anotherRejectedPromise: { state: 'rejected', reason: Error },
+ // }
+ // Note that for rejectedPromise, reason.message == 'rejection',
+ // and for anotherRejectedPromise, reason.message == 'more rejection'.
+ });
+ ```
+
+ An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that
+ are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype
+ chains.
+
+ Example:
+
+ ```javascript
+ function MyConstructor(){
+ this.example = RSVP.Promise.resolve('Example');
+ }
+
+ MyConstructor.prototype = {
+ protoProperty: RSVP.Promise.resolve('Proto Property')
+ };
+
+ let myObject = new MyConstructor();
+
+ RSVP.hashSettled(myObject).then(function(hash){
+ // protoProperty will not be present, instead you will just have an
+ // object that looks like:
+ // {
+ // example: { state: 'fulfilled', value: 'Example' }
+ // }
+ //
+ // hash.hasOwnProperty('protoProperty'); // false
+ // 'undefined' === typeof hash.protoProperty
+ });
+ ```
+
+ @method hashSettled
+ @for RSVP
+ @param {Object} object
+ @param {String} label optional string that describes the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when when all properties of `promises`
+ have been settled.
+ @static
+*/
+function hashSettled(object, label) {
+ return new HashSettled(Promise, object, label).promise;
+}
+
+function rethrow(reason) {
+ setTimeout(function () {
+ throw reason;
+ });
+ throw reason;
+}
+
+/**
+ `RSVP.defer` returns an object similar to jQuery's `$.Deferred`.
+ `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
+ interface. New code should use the `RSVP.Promise` constructor instead.
+
+ The object returned from `RSVP.defer` is a plain object with three properties:
+
+ * promise - an `RSVP.Promise`.
+ * reject - a function that causes the `promise` property on this object to
+ become rejected
+ * resolve - a function that causes the `promise` property on this object to
+ become fulfilled.
+
+ Example:
+
+ ```javascript
+ let deferred = RSVP.defer();
+
+ deferred.resolve("Success!");
+
+ deferred.promise.then(function(value){
+ // value here is "Success!"
+ });
+ ```
+
+ @method defer
+ @static
+ @for RSVP
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Object}
+ */
+function defer(label) {
+ var deferred = { resolve: undefined, reject: undefined };
+
+ deferred.promise = new Promise(function (resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ }, label);
+
+ return deferred;
+}
+
+/**
+ `RSVP.map` is similar to JavaScript's native `map` method, except that it
+ waits for all promises to become fulfilled before running the `mapFn` on
+ each item in given to `promises`. `RSVP.map` returns a promise that will
+ become fulfilled with the result of running `mapFn` on the values the promises
+ become fulfilled with.
+
+ For example:
+
+ ```javascript
+
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.resolve(2);
+ let promise3 = RSVP.resolve(3);
+ let promises = [ promise1, promise2, promise3 ];
+
+ let mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(result){
+ // result is [ 2, 3, 4 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.map` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.reject(new Error('2'));
+ let promise3 = RSVP.reject(new Error('3'));
+ let promises = [ promise1, promise2, promise3 ];
+
+ let mapFn = function(item){
+ return item + 1;
+ };
+
+ RSVP.map(promises, mapFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === '2'
+ });
+ ```
+
+ `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
+ say you want to get all comments from a set of blog posts, but you need
+ the blog posts first because they contain a url to those comments.
+
+ ```javscript
+
+ let mapFn = function(blogPost){
+ // getComments does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some comments data
+ return getComments(blogPost.comments_url);
+ };
+
+ // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
+ // with some blog post data
+ RSVP.map(getBlogPosts(), mapFn).then(function(comments){
+ // comments is the result of asking the server for the comments
+ // of all blog posts returned from getBlogPosts()
+ });
+ ```
+
+ @method map
+ @static
+ @for RSVP
+ @param {Array} promises
+ @param {Function} mapFn function to be called on each fulfilled promise.
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled with the result of calling
+ `mapFn` on each fulfilled promise or value when they become fulfilled.
+ The promise will be rejected if any of the given `promises` become rejected.
+ @static
+*/
+function map(promises, mapFn, label) {
+ return Promise.all(promises, label).then(function (values) {
+ if (!isFunction(mapFn)) {
+ throw new TypeError("You must pass a function as map's second argument.");
+ }
+
+ var length = values.length;
+ var results = new Array(length);
+
+ for (var i = 0; i < length; i++) {
+ results[i] = mapFn(values[i]);
+ }
+
+ return Promise.all(results, label);
+ });
+}
+
+/**
+ This is a convenient alias for `RSVP.Promise.resolve`.
+
+ @method resolve
+ @static
+ @for RSVP
+ @param {*} value value that the returned promise will be resolved with
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+*/
+function resolve$2(value, label) {
+ return Promise.resolve(value, label);
+}
+
+/**
+ This is a convenient alias for `RSVP.Promise.reject`.
+
+ @method reject
+ @static
+ @for RSVP
+ @param {*} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+*/
+function reject$2(reason, label) {
+ return Promise.reject(reason, label);
+}
+
+/**
+ `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
+ waits for all promises to become fulfilled before running the `filterFn` on
+ each item in given to `promises`. `RSVP.filter` returns a promise that will
+ become fulfilled with the result of running `filterFn` on the values the
+ promises become fulfilled with.
+
+ For example:
+
+ ```javascript
+
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.resolve(2);
+ let promise3 = RSVP.resolve(3);
+
+ let promises = [promise1, promise2, promise3];
+
+ let filterFn = function(item){
+ return item > 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(result){
+ // result is [ 2, 3 ]
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.filter` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promise's
+ rejection handler. For example:
+
+ ```javascript
+ let promise1 = RSVP.resolve(1);
+ let promise2 = RSVP.reject(new Error('2'));
+ let promise3 = RSVP.reject(new Error('3'));
+ let promises = [ promise1, promise2, promise3 ];
+
+ let filterFn = function(item){
+ return item > 1;
+ };
+
+ RSVP.filter(promises, filterFn).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(reason) {
+ // reason.message === '2'
+ });
+ ```
+
+ `RSVP.filter` will also wait for any promises returned from `filterFn`.
+ For instance, you may want to fetch a list of users then return a subset
+ of those users based on some asynchronous operation:
+
+ ```javascript
+
+ let alice = { name: 'alice' };
+ let bob = { name: 'bob' };
+ let users = [ alice, bob ];
+
+ let promises = users.map(function(user){
+ return RSVP.resolve(user);
+ });
+
+ let filterFn = function(user){
+ // Here, Alice has permissions to create a blog post, but Bob does not.
+ return getPrivilegesForUser(user).then(function(privs){
+ return privs.can_create_blog_post === true;
+ });
+ };
+ RSVP.filter(promises, filterFn).then(function(users){
+ // true, because the server told us only Alice can create a blog post.
+ users.length === 1;
+ // false, because Alice is the only user present in `users`
+ users[0] === bob;
+ });
+ ```
+
+ @method filter
+ @static
+ @for RSVP
+ @param {Array} promises
+ @param {Function} filterFn - function to be called on each resolved value to
+ filter the final results.
+ @param {String} label optional string describing the promise. Useful for
+ tooling.
+ @return {Promise}
+*/
+
+function resolveAll(promises, label) {
+ return Promise.all(promises, label);
+}
+
+function resolveSingle(promise, label) {
+ return Promise.resolve(promise, label).then(function (promises) {
+ return resolveAll(promises, label);
+ });
+}
+function filter(promises, filterFn, label) {
+ var promise = isArray(promises) ? resolveAll(promises, label) : resolveSingle(promises, label);
+ return promise.then(function (values) {
+ if (!isFunction(filterFn)) {
+ throw new TypeError("You must pass a function as filter's second argument.");
+ }
+
+ var length = values.length;
+ var filtered = new Array(length);
+
+ for (var i = 0; i < length; i++) {
+ filtered[i] = filterFn(values[i]);
+ }
+
+ return resolveAll(filtered, label).then(function (filtered) {
+ var results = new Array(length);
+ var newLength = 0;
+
+ for (var i = 0; i < length; i++) {
+ if (filtered[i]) {
+ results[newLength] = values[i];
+ newLength++;
+ }
+ }
+
+ results.length = newLength;
+
+ return results;
+ });
+ });
+}
+
+var len = 0;
+var vertxNext = undefined;
+function asap(callback, arg) {
+ queue$1[len] = callback;
+ queue$1[len + 1] = arg;
+ len += 2;
+ if (len === 2) {
+ // If len is 1, that means that we need to schedule an async flush.
+ // If additional callbacks are queued before the queue is flushed, they
+ // will be processed by this flush that we are scheduling.
+ scheduleFlush$1();
+ }
+}
+
+var browserWindow = typeof window !== 'undefined' ? window : undefined;
+var browserGlobal = browserWindow || {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]';
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+ var nextTick = process.nextTick;
+ // node version 0.10.x displays a deprecation warning when nextTick is used recursively
+ // setImmediate should be used instead instead
+ var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
+ if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
+ nextTick = setImmediate;
+ }
+ return function () {
+ return nextTick(flush);
+ };
+}
+
+// vertx
+function useVertxTimer() {
+ if (typeof vertxNext !== 'undefined') {
+ return function () {
+ vertxNext(flush);
+ };
+ }
+ return useSetTimeout();
+}
+
+function useMutationObserver() {
+ var iterations = 0;
+ var observer = new BrowserMutationObserver(flush);
+ var node = document.createTextNode('');
+ observer.observe(node, { characterData: true });
+
+ return function () {
+ return node.data = iterations = ++iterations % 2;
+ };
+}
+
+// web worker
+function useMessageChannel() {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = flush;
+ return function () {
+ return channel.port2.postMessage(0);
+ };
+}
+
+function useSetTimeout() {
+ return function () {
+ return setTimeout(flush, 1);
+ };
+}
+
+var queue$1 = new Array(1000);
+
+function flush() {
+ for (var i = 0; i < len; i += 2) {
+ var callback = queue$1[i];
+ var arg = queue$1[i + 1];
+
+ callback(arg);
+
+ queue$1[i] = undefined;
+ queue$1[i + 1] = undefined;
+ }
+
+ len = 0;
+}
+
+function attemptVertex() {
+ try {
+ var r = require;
+ var vertx = r('vertx');
+ vertxNext = vertx.runOnLoop || vertx.runOnContext;
+ return useVertxTimer();
+ } catch (e) {
+ return useSetTimeout();
+ }
+}
+
+var scheduleFlush$1 = undefined;
+// Decide what async method to use to triggering processing of queued callbacks:
+if (isNode) {
+ scheduleFlush$1 = useNextTick();
+} else if (BrowserMutationObserver) {
+ scheduleFlush$1 = useMutationObserver();
+} else if (isWorker) {
+ scheduleFlush$1 = useMessageChannel();
+} else if (browserWindow === undefined && typeof require === 'function') {
+ scheduleFlush$1 = attemptVertex();
+} else {
+ scheduleFlush$1 = useSetTimeout();
+}
+
+var platform = undefined;
+
+/* global self */
+if (typeof self === 'object') {
+ platform = self;
+
+ /* global global */
+} else if (typeof global === 'object') {
+ platform = global;
+ } else {
+ throw new Error('no global: `self` or `global` found');
+ }
+
+var _async$filter;
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+// defaults
+
+// the default export here is for backwards compat:
+// https://github.com/tildeio/rsvp.js/issues/434
+config.async = asap;
+config.after = function (cb) {
+ return setTimeout(cb, 0);
+};
+var cast = resolve$2;
+
+var async = function async(callback, arg) {
+ return config.async(callback, arg);
+};
+
+function on() {
+ config['on'].apply(config, arguments);
+}
+
+function off() {
+ config['off'].apply(config, arguments);
+}
+
+// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
+if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
+ var callbacks = window['__PROMISE_INSTRUMENTATION__'];
+ configure('instrument', true);
+ for (var eventName in callbacks) {
+ if (callbacks.hasOwnProperty(eventName)) {
+ on(eventName, callbacks[eventName]);
+ }
+ }
+}var rsvp = (_async$filter = {
+ cast: cast,
+ Promise: Promise,
+ EventTarget: EventTarget,
+ all: all$1,
+ allSettled: allSettled,
+ race: race$1,
+ hash: hash,
+ hashSettled: hashSettled,
+ rethrow: rethrow,
+ defer: defer,
+ denodeify: denodeify,
+ configure: configure,
+ on: on,
+ off: off,
+ resolve: resolve$2,
+ reject: reject$2,
+ map: map
+}, _defineProperty(_async$filter, 'async', async), _defineProperty(_async$filter, 'filter', // babel seems to error if async isn't a computed prop here...
+filter), _async$filter);
+
+exports['default'] = rsvp;
+exports.cast = cast;
+exports.Promise = Promise;
+exports.EventTarget = EventTarget;
+exports.all = all$1;
+exports.allSettled = allSettled;
+exports.race = race$1;
+exports.hash = hash;
+exports.hashSettled = hashSettled;
+exports.rethrow = rethrow;
+exports.defer = defer;
+exports.denodeify = denodeify;
+exports.configure = configure;
+exports.on = on;
+exports.off = off;
+exports.resolve = resolve$2;
+exports.reject = reject$2;
+exports.map = map;
+exports.async = async;
+exports.filter = filter;
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+})));
+//
+'use strict';
+
+var EPUBJS = EPUBJS || {};
+EPUBJS.VERSION = "0.2.15";
+
+EPUBJS.plugins = EPUBJS.plugins || {};
+
+EPUBJS.filePath = EPUBJS.filePath || "/epubjs/";
+
+EPUBJS.Render = {};
+
+(function(root) {
+
+ var previousEpub = root.ePub || {};
+
+ var ePub = root.ePub = function() {
+ var bookPath, options;
+
+ //-- var book = ePub("path/to/book.epub", { restore: true })
+ if(typeof(arguments[0]) != 'undefined' &&
+ (typeof arguments[0] === 'string' || arguments[0] instanceof ArrayBuffer)) {
+
+ bookPath = arguments[0];
+
+ if( arguments[1] && typeof arguments[1] === 'object' ) {
+ options = arguments[1];
+ options.bookPath = bookPath;
+ } else {
+ options = { 'bookPath' : bookPath };
+ }
+
+ }
+
+ /*
+ * var book = ePub({ bookPath: "path/to/book.epub", restore: true });
+ *
+ * - OR -
+ *
+ * var book = ePub({ restore: true });
+ * book.open("path/to/book.epub");
+ */
+
+ if( arguments[0] && typeof arguments[0] === 'object' && !(arguments[0] instanceof ArrayBuffer)) {
+ options = arguments[0];
+ }
+
+
+ return new EPUBJS.Book(options);
+ };
+
+ //exports to multiple environments
+ if (typeof define === 'function' && define.amd) {
+ //AMD
+ define(['rsvp', 'jszip', 'localforage'], function(RSVP, JSZip, localForage){ return ePub; });
+ } else if (typeof module != "undefined" && module.exports) {
+ //Node
+ global.RSVP = require('rsvp');
+ global.JSZip = require('jszip');
+ global.localForage = require('localforage');
+ module.exports = ePub;
+ }
+
+})(window);
+
+EPUBJS.Book = function(options){
+
+ var book = this;
+
+ this.settings = EPUBJS.core.defaults(options || {}, {
+ bookPath : undefined,
+ bookKey : undefined,
+ packageUrl : undefined,
+ storage: false, //-- true (auto) or false (none) | override: 'ram', 'websqldatabase', 'indexeddb', 'filesystem'
+ fromStorage : false,
+ saved : false,
+ online : true,
+ contained : false,
+ width : undefined,
+ height: undefined,
+ layoutOveride : undefined, // Default: { spread: 'reflowable', layout: 'auto', orientation: 'auto'}
+ orientation : undefined,
+ minSpreadWidth: 768, //-- overridden by spread: none (never) / both (always)
+ gap: "auto", //-- "auto" or int
+ version: 1,
+ restore: false,
+ reload : false,
+ goto : false,
+ styles : {},
+ headTags : {},
+ withCredentials: false,
+ render_method: "Iframe",
+ displayLastPage: false
+ });
+
+ this.settings.EPUBJSVERSION = EPUBJS.VERSION;
+
+ this.spinePos = 0;
+ this.stored = false;
+
+ //-- All Book events for listening
+ /*
+ book:ready
+ book:stored
+ book:online
+ book:offline
+ book:pageChanged
+ book:loadFailed
+ book:loadChapterFailed
+ */
+
+ //-- Adds Hook methods to the Book prototype
+ // Hooks will all return before triggering the callback.
+ // EPUBJS.Hooks.mixin(this);
+ //-- Get pre-registered hooks for events
+ // this.getHooks("beforeChapterDisplay");
+
+ this.online = this.settings.online || navigator.onLine;
+ this.networkListeners();
+
+ this.ready = {
+ manifest: new RSVP.defer(),
+ spine: new RSVP.defer(),
+ metadata: new RSVP.defer(),
+ cover: new RSVP.defer(),
+ toc: new RSVP.defer(),
+ pageList: new RSVP.defer()
+ };
+
+ this.readyPromises = [
+ this.ready.manifest.promise,
+ this.ready.spine.promise,
+ this.ready.metadata.promise,
+ this.ready.cover.promise,
+ this.ready.toc.promise
+ ];
+
+ this.pageList = [];
+ this.pagination = new EPUBJS.Pagination();
+ this.pageListReady = this.ready.pageList.promise;
+
+ this.ready.all = RSVP.all(this.readyPromises);
+
+ this.ready.all.then(this._ready.bind(this));
+
+ // Queue for methods used before rendering
+ this.isRendered = false;
+ this._q = EPUBJS.core.queue(this);
+ // Queue for rendering
+ this._rendering = false;
+ this._displayQ = EPUBJS.core.queue(this);
+ // Queue for going to another location
+ this._moving = false;
+ this._gotoQ = EPUBJS.core.queue(this);
+
+ /**
+ * Creates a new renderer.
+ * The renderer will handle displaying the content using the method provided in the settings
+ */
+ this.renderer = new EPUBJS.Renderer(this.settings.render_method);
+ //-- Set the width at which to switch from spreads to single pages
+ this.renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
+ this.renderer.setGap(this.settings.gap);
+ //-- Pass through the renderer events
+ this.listenToRenderer(this.renderer);
+
+ this.defer_opened = new RSVP.defer();
+ this.opened = this.defer_opened.promise;
+
+ this.store = false; //-- False if not using storage;
+
+ //-- Determine storage method
+ //-- Override options: none | ram | websqldatabase | indexeddb | filesystem
+ if(this.settings.storage !== false){
+ // this.storage = new fileStorage.storage(this.settings.storage);
+ this.fromStorage(true);
+ }
+
+ // BookUrl is optional, but if present start loading process
+ if(typeof this.settings.bookPath === 'string' || this.settings.bookPath instanceof ArrayBuffer) {
+ this.open(this.settings.bookPath, this.settings.reload);
+ }
+
+ window.addEventListener("beforeunload", this.unload.bind(this), false);
+
+ //-- Listen for these promises:
+ //-- book.opened.then()
+ //-- book.rendered.then()
+};
+
+//-- Check bookUrl and start parsing book Assets or load them from storage
+EPUBJS.Book.prototype.open = function(bookPath, forceReload){
+ var book = this,
+ epubpackage,
+ opened = new RSVP.defer();
+
+ this.settings.bookPath = bookPath;
+
+ if(this.settings.contained || this.isContained(bookPath)){
+
+ this.settings.contained = this.contained = true;
+
+ this.bookUrl = '';
+
+ epubpackage = this.unarchive(bookPath).
+ then(function(){
+ return book.loadPackage();
+ });
+
+ } else {
+ //-- Get a absolute URL from the book path
+ this.bookUrl = this.urlFrom(bookPath);
+
+ epubpackage = this.loadPackage();
+ }
+
+ if(this.settings.restore && !forceReload && localStorage){
+ //-- Will load previous package json, or re-unpack if error
+ epubpackage.then(function(packageXml) {
+ var identifier = book.packageIdentifier(packageXml);
+ var restored = book.restore(identifier);
+
+ if(!restored) {
+ book.unpack(packageXml);
+ }
+ opened.resolve();
+ book.defer_opened.resolve();
+ });
+
+ }else{
+
+ //-- Get package information from epub opf
+ epubpackage.then(function(packageXml) {
+ book.unpack(packageXml);
+ opened.resolve();
+ book.defer_opened.resolve();
+ });
+ }
+
+ this._registerReplacements(this.renderer);
+
+ return opened.promise;
+
+};
+
+EPUBJS.Book.prototype.loadPackage = function(_containerPath){
+ var book = this,
+ parse = new EPUBJS.Parser(),
+ containerPath = _containerPath || "META-INF/container.xml",
+ containerXml,
+ packageXml;
+
+ if(!this.settings.packageUrl) { //-- provide the packageUrl to skip this step
+ packageXml = book.loadXml(book.bookUrl + containerPath).
+ then(function(containerXml){
+ return parse.container(containerXml); // Container has path to content
+ }).
+ then(function(paths){
+ book.settings.contentsPath = book.bookUrl + paths.basePath;
+ book.settings.packageUrl = book.bookUrl + paths.packagePath;
+ book.settings.encoding = paths.encoding;
+ return book.loadXml(book.settings.packageUrl); // Containes manifest, spine and metadata
+ });
+ } else {
+ packageXml = book.loadXml(book.settings.packageUrl);
+ }
+
+ packageXml.catch(function(error) {
+ // handle errors in either of the two requests
+ console.error("Could not load book at: "+ containerPath);
+ book.trigger("book:loadFailed", containerPath);
+ });
+ return packageXml;
+};
+
+EPUBJS.Book.prototype.packageIdentifier = function(packageXml){
+ var book = this,
+ parse = new EPUBJS.Parser();
+
+ return parse.identifier(packageXml);
+};
+
+EPUBJS.Book.prototype.unpack = function(packageXml){
+ var book = this,
+ parse = new EPUBJS.Parser();
+
+ book.contents = parse.packageContents(packageXml, book.settings.contentsPath); // Extract info from contents
+
+ book.manifest = book.contents.manifest;
+ book.spine = book.contents.spine;
+ book.spineIndexByURL = book.contents.spineIndexByURL;
+ book.metadata = book.contents.metadata;
+ if(!book.settings.bookKey) {
+ book.settings.bookKey = book.generateBookKey(book.metadata.identifier);
+ }
+
+ //-- Set Globbal Layout setting based on metadata
+ book.globalLayoutProperties = book.parseLayoutProperties(book.metadata);
+
+ if(book.contents.coverPath) {
+ book.cover = book.contents.cover = book.settings.contentsPath + book.contents.coverPath;
+ }
+
+ book.spineNodeIndex = book.contents.spineNodeIndex;
+
+ book.ready.manifest.resolve(book.contents.manifest);
+ book.ready.spine.resolve(book.contents.spine);
+ book.ready.metadata.resolve(book.contents.metadata);
+ book.ready.cover.resolve(book.contents.cover);
+
+ book.locations = new EPUBJS.Locations(book.spine, book.store, book.settings.withCredentials);
+
+ //-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
+ if(book.contents.navPath) {
+ book.settings.navUrl = book.settings.contentsPath + book.contents.navPath;
+
+ book.loadXml(book.settings.navUrl).
+ then(function(navHtml){
+ return parse.nav(navHtml, book.spineIndexByURL, book.spine); // Grab Table of Contents
+ }).then(function(toc){
+ book.toc = book.contents.toc = toc;
+ book.ready.toc.resolve(book.contents.toc);
+ }, function(error) {
+ book.ready.toc.resolve(false);
+ });
+
+ // Load the optional pageList
+ book.loadXml(book.settings.navUrl).
+ then(function(navHtml){
+ return parse.pageList(navHtml, book.spineIndexByURL, book.spine);
+ }).then(function(pageList){
+ var epubcfi = new EPUBJS.EpubCFI();
+ var wait = 0; // need to generate a cfi
+
+ // No pageList found
+ if(pageList.length === 0) {
+ return;
+ }
+
+ book.pageList = book.contents.pageList = pageList;
+
+ // Replace HREFs with CFI
+ book.pageList.forEach(function(pg){
+ if(!pg.cfi) {
+ wait += 1;
+ epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
+ pg.cfi = cfi;
+ pg.packageUrl = book.settings.packageUrl;
+
+ wait -= 1;
+ if(wait === 0) {
+ book.pagination.process(book.pageList);
+ book.ready.pageList.resolve(book.pageList);
+ }
+ });
+ }
+ });
+
+ if(!wait) {
+ book.pagination.process(book.pageList);
+ book.ready.pageList.resolve(book.pageList);
+ }
+
+ }, function(error) {
+ book.ready.pageList.resolve([]);
+ });
+ } else if(book.contents.tocPath) {
+ book.settings.tocUrl = book.settings.contentsPath + book.contents.tocPath;
+
+ book.loadXml(book.settings.tocUrl).
+ then(function(tocXml){
+ return parse.toc(tocXml, book.spineIndexByURL, book.spine); // Grab Table of Contents
+ }, function(err) {
+ console.error(err);
+ }).then(function(toc){
+ book.toc = book.contents.toc = toc;
+ book.ready.toc.resolve(book.contents.toc);
+ }, function(error) {
+ book.ready.toc.resolve(false);
+ });
+
+ } else {
+ book.ready.toc.resolve(false);
+ }
+
+};
+
+EPUBJS.Book.prototype.createHiddenRender = function(renderer, _width, _height) {
+ var box = this.element.getBoundingClientRect();
+ var width = _width || this.settings.width || box.width;
+ var height = _height || this.settings.height || box.height;
+ var hiddenContainer;
+ var hiddenEl;
+ renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
+ renderer.setGap(this.settings.gap);
+
+ this._registerReplacements(renderer);
+ if(this.settings.forceSingle) {
+ renderer.forceSingle(true);
+ }
+
+ hiddenContainer = document.createElement("div");
+ hiddenContainer.style.visibility = "hidden";
+ hiddenContainer.style.overflow = "hidden";
+ hiddenContainer.style.width = "0";
+ hiddenContainer.style.height = "0";
+ this.element.appendChild(hiddenContainer);
+
+ hiddenEl = document.createElement("div");
+ hiddenEl.style.visibility = "hidden";
+ hiddenEl.style.overflow = "hidden";
+ hiddenEl.style.width = width + "px";//"0";
+ hiddenEl.style.height = height +"px"; //"0";
+ hiddenContainer.appendChild(hiddenEl);
+
+ renderer.initialize(hiddenEl, this.settings.width, this.settings.height);
+ return hiddenContainer;
+};
+
+// Generates the pageList array by loading every chapter and paging through them
+EPUBJS.Book.prototype.generatePageList = function(width, height, flag){
+ var pageList = [];
+ var pager = new EPUBJS.Renderer(this.settings.render_method, false); //hidden
+ var hiddenContainer = this.createHiddenRender(pager, width, height);
+ var deferred = new RSVP.defer();
+ var spinePos = -1;
+ var spineLength = this.spine.length;
+ var totalPages = 0;
+ var currentPage = 0;
+ var nextChapter = function(deferred){
+ var chapter;
+ var next = spinePos + 1;
+ var done = deferred || new RSVP.defer();
+ var loaded;
+ if(next >= spineLength) {
+ done.resolve();
+ } else {
+ if (flag && flag.cancelled) {
+ pager.remove();
+ this.element.removeChild(hiddenContainer);
+ done.reject(new Error("User cancelled"));
+ return;
+ }
+
+ spinePos = next;
+ chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
+ pager.displayChapter(chapter, this.globalLayoutProperties).then(function(chap){
+ pager.pageMap.forEach(function(item){
+ currentPage += 1;
+ pageList.push({
+ "cfi" : item.start,
+ "page" : currentPage
+ });
+
+ });
+
+ if(pager.pageMap.length % 2 > 0 &&
+ pager.spreads) {
+ currentPage += 1; // Handle Spreads
+ pageList.push({
+ "cfi" : pager.pageMap[pager.pageMap.length - 1].end,
+ "page" : currentPage
+ });
+ }
+
+ // Load up the next chapter
+ setTimeout(function(){
+ nextChapter(done);
+ }, 1);
+ });
+ }
+ return done.promise;
+ }.bind(this);
+
+ var finished = nextChapter().then(function(){
+ pager.remove();
+ this.element.removeChild(hiddenContainer);
+ deferred.resolve(pageList);
+ }.bind(this), function(reason) {
+ deferred.reject(reason);
+ });
+
+ return deferred.promise;
+};
+
+// Render out entire book and generate the pagination
+// Width and Height are optional and will default to the current dimensions
+EPUBJS.Book.prototype.generatePagination = function(width, height, flag) {
+ var book = this;
+ var defered = new RSVP.defer();
+
+ this.ready.spine.promise.then(function(){
+ book.generatePageList(width, height, flag).then(function(pageList){
+ book.pageList = book.contents.pageList = pageList;
+ book.pagination.process(pageList);
+ book.ready.pageList.resolve(book.pageList);
+ defered.resolve(book.pageList);
+ }, function(reason) {
+ defered.reject(reason);
+ });
+ });
+
+ return defered.promise;
+};
+
+// Process the pagination from a JSON array containing the pagelist
+EPUBJS.Book.prototype.loadPagination = function(pagelistJSON) {
+ var pageList;
+
+ if (typeof(pagelistJSON) === "string") {
+ pageList = JSON.parse(pagelistJSON);
+ } else {
+ pageList = pagelistJSON;
+ }
+
+ if(pageList && pageList.length) {
+ this.pageList = pageList;
+ this.pagination.process(this.pageList);
+ this.ready.pageList.resolve(this.pageList);
+ }
+ return this.pageList;
+};
+
+EPUBJS.Book.prototype.getPageList = function() {
+ return this.ready.pageList.promise;
+};
+
+EPUBJS.Book.prototype.getMetadata = function() {
+ return this.ready.metadata.promise;
+};
+
+EPUBJS.Book.prototype.getToc = function() {
+ return this.ready.toc.promise;
+};
+
+/* Private Helpers */
+
+//-- Listeners for browser events
+EPUBJS.Book.prototype.networkListeners = function(){
+ var book = this;
+ window.addEventListener("offline", function(e) {
+ book.online = false;
+ if (book.settings.storage) {
+ book.fromStorage(true);
+ }
+ book.trigger("book:offline");
+ }, false);
+
+ window.addEventListener("online", function(e) {
+ book.online = true;
+ if (book.settings.storage) {
+ book.fromStorage(false);
+ }
+ book.trigger("book:online");
+ }, false);
+
+};
+
+// Listen to all events the renderer triggers and pass them as book events
+EPUBJS.Book.prototype.listenToRenderer = function(renderer){
+ var book = this;
+ renderer.Events.forEach(function(eventName){
+ renderer.on(eventName, function(e){
+ book.trigger(eventName, e);
+ });
+ });
+
+ renderer.on("renderer:visibleRangeChanged", function(range) {
+ var startPage, endPage, percent;
+ var pageRange = [];
+
+ if(this.pageList.length > 0) {
+ startPage = this.pagination.pageFromCfi(range.start);
+ percent = this.pagination.percentageFromPage(startPage);
+ pageRange.push(startPage);
+
+ if(range.end) {
+ endPage = this.pagination.pageFromCfi(range.end);
+ //if(startPage != endPage) {
+ pageRange.push(endPage);
+ //}
+ }
+ this.trigger("book:pageChanged", {
+ "anchorPage": startPage,
+ "percentage": percent,
+ "pageRange" : pageRange
+ });
+
+ // TODO: Add event for first and last page.
+ // (though last is going to be hard, since it could be several reflowed pages long)
+ }
+ }.bind(this));
+
+ renderer.on("render:loaded", this.loadChange.bind(this));
+};
+
+// Listens for load events from the Renderer and checks against the current chapter
+// Prevents the Render from loading a different chapter when back button is pressed
+EPUBJS.Book.prototype.loadChange = function(url){
+ var uri = EPUBJS.core.uri(url);
+ var chapterUri = EPUBJS.core.uri(this.currentChapter.absolute);
+ var spinePos, chapter;
+
+ if(uri.path != chapterUri.path){
+ console.warn("Miss Match", uri.path, this.currentChapter.absolute);
+ // this.goto(uri.filename);
+
+ // Set the current chapter to what is being displayed
+ spinePos = this.spineIndexByURL[uri.filename];
+ chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
+ this.currentChapter = chapter;
+
+ // setup the renderer with the displayed chapter
+ this.renderer.currentChapter = chapter;
+ this.renderer.afterLoad(this.renderer.render.docEl);
+ this.renderer.beforeDisplay(function () {
+ this.renderer.afterDisplay();
+ }.bind(this));
+
+ } else if(!this._rendering) {
+ this.renderer.reformat();
+ }
+};
+
+EPUBJS.Book.prototype.unlistenToRenderer = function(renderer){
+ renderer.Events.forEach(function(eventName){
+ renderer.off(eventName);
+ });
+};
+
+//-- Returns the cover
+EPUBJS.Book.prototype.coverUrl = function(){
+ var retrieved = this.ready.cover.promise
+ .then(function(url) {
+ if(this.settings.fromStorage) {
+ return this.store.getUrl(this.contents.cover);
+ } else if(this.settings.contained) {
+ return this.zip.getUrl(this.contents.cover);
+ }else{
+ return this.contents.cover;
+ }
+ }.bind(this));
+
+ retrieved.then(function(url) {
+ this.cover = url;
+ }.bind(this));
+
+ return retrieved;
+};
+
+//-- Choose between a request from store or a request from network
+EPUBJS.Book.prototype.loadXml = function(url){
+ if(this.settings.fromStorage) {
+ return this.store.getXml(url, this.settings.encoding);
+ } else if(this.settings.contained) {
+ return this.zip.getXml(url, this.settings.encoding);
+ }else{
+ return EPUBJS.core.request(url, 'xml', this.settings.withCredentials);
+ }
+};
+
+//-- Turns a url into a absolute url
+EPUBJS.Book.prototype.urlFrom = function(bookPath){
+ var uri = EPUBJS.core.uri(bookPath),
+ absolute = uri.protocol,
+ fromRoot = uri.path[0] == "/",
+ location = window.location,
+ //-- Get URL orgin, try for native or combine
+ origin = location.origin || location.protocol + "//" + location.host,
+ baseTag = document.getElementsByTagName('base'),
+ base;
+
+
+ //-- Check is Base tag is set
+
+ if(baseTag.length) {
+ base = baseTag[0].href;
+ }
+
+ //-- 1. Check if url is absolute
+ if(uri.protocol){
+ return uri.origin + uri.path;
+ }
+
+ //-- 2. Check if url starts with /, add base url
+ if(!absolute && fromRoot){
+ return (base || origin) + uri.path;
+ }
+
+ //-- 3. Or find full path to url and add that
+ if(!absolute && !fromRoot){
+ return EPUBJS.core.resolveUrl(base || location.pathname, uri.path);
+ }
+
+};
+
+
+EPUBJS.Book.prototype.unarchive = function(bookPath){
+ var book = this,
+ unarchived;
+
+ //-- Must use storage
+ // if(this.settings.storage == false ){
+ // this.settings.storage = true;
+ // this.storage = new fileStorage.storage();
+ // }
+
+ this.zip = new EPUBJS.Unarchiver();
+ this.store = this.zip; // Use zip storaged in ram
+ return this.zip.open(bookPath);
+};
+
+//-- Checks if url has a .epub or .zip extension, or is ArrayBuffer (of zip/epub)
+EPUBJS.Book.prototype.isContained = function(bookUrl){
+ if (bookUrl instanceof ArrayBuffer) {
+ return true;
+ }
+ var uri = EPUBJS.core.uri(bookUrl);
+
+ if(uri.extension && (uri.extension == "epub" || uri.extension == "zip")){
+ return true;
+ }
+
+ return false;
+};
+
+//-- Checks if the book can be retrieved from localStorage
+EPUBJS.Book.prototype.isSaved = function(bookKey) {
+ var storedSettings;
+
+ if(!localStorage) {
+ return false;
+ }
+
+ storedSettings = localStorage.getItem(bookKey);
+
+ if( !localStorage ||
+ storedSettings === null) {
+ return false;
+ } else {
+ return true;
+ }
+};
+
+// Generates the Book Key using the identifer in the manifest or other string provided
+EPUBJS.Book.prototype.generateBookKey = function(identifier){
+ return "epubjs:" + EPUBJS.VERSION + ":" + window.location.host + ":" + identifier;
+};
+
+EPUBJS.Book.prototype.saveContents = function(){
+ if(!localStorage) {
+ return false;
+ }
+ localStorage.setItem(this.settings.bookKey, JSON.stringify(this.contents));
+};
+
+EPUBJS.Book.prototype.removeSavedContents = function() {
+ if(!localStorage) {
+ return false;
+ }
+ localStorage.removeItem(this.settings.bookKey);
+};
+
+
+
+//-- Takes a string or a element
+EPUBJS.Book.prototype.renderTo = function(elem){
+ var book = this,
+ rendered;
+
+ if(EPUBJS.core.isElement(elem)) {
+ this.element = elem;
+ } else if (typeof elem == "string") {
+ this.element = EPUBJS.core.getEl(elem);
+ } else {
+ console.error("Not an Element");
+ return;
+ }
+
+ rendered = this.opened.
+ then(function(){
+ // book.render = new EPUBJS.Renderer[this.settings.renderer](book);
+ book.renderer.initialize(book.element, book.settings.width, book.settings.height);
+
+ if(book.metadata.direction) {
+ book.renderer.setDirection(book.metadata.direction);
+ }
+
+ book._rendered();
+ return book.startDisplay();
+ });
+
+ // rendered.then(null, function(error) { console.error(error); });
+
+ return rendered;
+};
+
+EPUBJS.Book.prototype.startDisplay = function(){
+ var display;
+
+ if(this.settings.goto) {
+ display = this.goto(this.settings.goto);
+ }else if(this.settings.previousLocationCfi) {
+ display = this.gotoCfi(this.settings.previousLocationCfi);
+ }else{
+ display = this.displayChapter(this.spinePos, this.settings.displayLastPage);
+ }
+
+ return display;
+};
+
+EPUBJS.Book.prototype.restore = function(identifier){
+
+ var book = this,
+ fetch = ['manifest', 'spine', 'metadata', 'cover', 'toc', 'spineNodeIndex', 'spineIndexByURL', 'globalLayoutProperties'],
+ reject = false,
+ bookKey = this.generateBookKey(identifier),
+ fromStore = localStorage.getItem(bookKey),
+ len = fetch.length,
+ i;
+
+ if(this.settings.clearSaved) reject = true;
+
+ if(!reject && fromStore != 'undefined' && fromStore !== null){
+ book.contents = JSON.parse(fromStore);
+
+ for(i = 0; i < len; i++) {
+ var item = fetch[i];
+
+ if(!book.contents[item]) {
+ reject = true;
+ break;
+ }
+ book[item] = book.contents[item];
+ }
+ }
+
+ if(reject || !fromStore || !this.contents || !this.settings.contentsPath){
+ return false;
+ }else{
+ this.settings.bookKey = bookKey;
+ this.ready.manifest.resolve(this.manifest);
+ this.ready.spine.resolve(this.spine);
+ this.ready.metadata.resolve(this.metadata);
+ this.ready.cover.resolve(this.cover);
+ this.ready.toc.resolve(this.toc);
+ return true;
+ }
+
+};
+
+EPUBJS.Book.prototype.displayChapter = function(chap, end, deferred){
+ var book = this,
+ render,
+ cfi,
+ pos,
+ store,
+ defer = deferred || new RSVP.defer();
+
+ var chapter;
+
+ if(!this.isRendered) {
+ this._q.enqueue("displayChapter", arguments);
+ // Reject for now. TODO: pass promise to queue
+ defer.reject({
+ message : "Rendering",
+ stack : new Error().stack
+ });
+ return defer.promise;
+ }
+
+
+ if(this._rendering || this.renderer._moving) {
+ // Pass along the current defer
+ this._displayQ.enqueue("displayChapter", [chap, end, defer]);
+ return defer.promise;
+ }
+
+ if(EPUBJS.core.isNumber(chap)){
+ pos = chap;
+ }else{
+ cfi = new EPUBJS.EpubCFI(chap);
+ pos = cfi.spinePos;
+ }
+
+ if(pos < 0 || pos >= this.spine.length){
+ console.warn("Not A Valid Location");
+ pos = 0;
+ end = false;
+ cfi = false;
+ }
+
+ //-- Create a new chapter
+ chapter = new EPUBJS.Chapter(this.spine[pos], this.store);
+
+ this._rendering = true;
+
+ if(this._needsAssetReplacement()) {
+
+ chapter.registerHook("beforeChapterRender", [
+ EPUBJS.replace.head,
+ EPUBJS.replace.resources,
+ EPUBJS.replace.svg
+ ], true);
+
+ }
+
+ book.currentChapter = chapter;
+
+ render = book.renderer.displayChapter(chapter, this.globalLayoutProperties);
+ if(cfi) {
+ book.renderer.gotoCfi(cfi);
+ } else if(end) {
+ book.renderer.lastPage();
+ }
+ //-- Success, Clear render queue
+ render.then(function(rendered){
+ // var inwait;
+ //-- Set the book's spine position
+ book.spinePos = pos;
+
+ defer.resolve(book.renderer);
+
+ if(book.settings.fromStorage === false &&
+ book.settings.contained === false) {
+ book.preloadNextChapter();
+ }
+
+ book._rendering = false;
+ book._displayQ.dequeue();
+ if(book._displayQ.length() === 0) {
+ book._gotoQ.dequeue();
+ }
+
+ }, function(error) {
+ // handle errors in either of the two requests
+ console.error("Could not load Chapter: "+ chapter.absolute, error);
+ book.trigger("book:chapterLoadFailed", chapter.absolute);
+ book._rendering = false;
+ defer.reject(error);
+ });
+
+ return defer.promise;
+};
+
+EPUBJS.Book.prototype.nextPage = function(defer){
+ var defer = defer || new RSVP.defer();
+
+ if (!this.isRendered) {
+ this._q.enqueue("nextPage", [defer]);
+ return defer.promise;
+ }
+
+ var next = this.renderer.nextPage();
+ if (!next){
+ return this.nextChapter(defer);
+ }
+
+ defer.resolve(true);
+ return defer.promise;
+};
+
+EPUBJS.Book.prototype.prevPage = function(defer) {
+ var defer = defer || new RSVP.defer();
+
+ if (!this.isRendered) {
+ this._q.enqueue("prevPage", [defer]);
+ return defer.promise;
+ }
+
+ var prev = this.renderer.prevPage();
+ if (!prev){
+ return this.prevChapter(defer);
+ }
+
+ defer.resolve(true);
+ return defer.promise;
+};
+
+EPUBJS.Book.prototype.nextChapter = function(defer) {
+ var defer = defer || new RSVP.defer();
+
+ if (this.spinePos < this.spine.length - 1) {
+ var next = this.spinePos + 1;
+ // Skip non linear chapters
+ while (this.spine[next] && this.spine[next].linear && this.spine[next].linear == 'no') {
+ next++;
+ }
+ if (next < this.spine.length) {
+ return this.displayChapter(next, false, defer);
+ }
+ }
+
+ this.trigger("book:atEnd");
+ defer.resolve(true);
+ return defer.promise;
+};
+
+EPUBJS.Book.prototype.prevChapter = function(defer) {
+ var defer = defer || new RSVP.defer();
+
+ if (this.spinePos > 0) {
+ var prev = this.spinePos - 1;
+ while (this.spine[prev] && this.spine[prev].linear && this.spine[prev].linear == 'no') {
+ prev--;
+ }
+ if (prev >= 0) {
+ return this.displayChapter(prev, true, defer);
+ }
+ }
+
+ this.trigger("book:atStart");
+ defer.resolve(true);
+ return defer.promise;
+};
+
+EPUBJS.Book.prototype.getCurrentLocationCfi = function() {
+ if(!this.isRendered) return false;
+ return this.renderer.currentLocationCfi;
+};
+
+EPUBJS.Book.prototype.goto = function(target){
+
+ if(target.indexOf("epubcfi(") === 0) {
+ return this.gotoCfi(target);
+ } else if(target.indexOf("%") === target.length-1) {
+ return this.gotoPercentage(parseInt(target.substring(0, target.length-1))/100);
+ } else if(typeof target === "number" || isNaN(target) === false){
+ return this.gotoPage(target);
+ } else {
+ return this.gotoHref(target);
+ }
+
+};
+
+EPUBJS.Book.prototype.gotoCfi = function(cfiString, defer){
+ var cfi,
+ spinePos,
+ spineItem,
+ rendered,
+ promise,
+ render,
+ deferred = defer || new RSVP.defer();
+
+ if(!this.isRendered) {
+ console.warn("Not yet Rendered");
+ this.settings.previousLocationCfi = cfiString;
+ return false;
+ }
+
+ // Currently going to a chapter
+ if(this._moving || this._rendering) {
+ console.warn("Renderer is moving");
+ this._gotoQ.enqueue("gotoCfi", [cfiString, deferred]);
+ return false;
+ }
+
+ cfi = new EPUBJS.EpubCFI(cfiString);
+ spinePos = cfi.spinePos;
+
+ if(spinePos == -1) {
+ return false;
+ }
+
+ spineItem = this.spine[spinePos];
+ promise = deferred.promise;
+ this._moving = true;
+ //-- If same chapter only stay on current chapter
+ if(this.currentChapter && this.spinePos === spinePos){
+ this.renderer.gotoCfi(cfi);
+ this._moving = false;
+ deferred.resolve(this.renderer.currentLocationCfi);
+ } else {
+
+ if(!spineItem || spinePos == -1) {
+ spinePos = 0;
+ spineItem = this.spine[spinePos];
+ }
+
+ render = this.displayChapter(cfiString);
+
+ render.then(function(rendered){
+ this._moving = false;
+ deferred.resolve(rendered.currentLocationCfi);
+ }.bind(this), function() {
+ this._moving = false;
+ }.bind(this));
+
+ }
+
+ promise.then(function(){
+ this._gotoQ.dequeue();
+ }.bind(this));
+
+ return promise;
+};
+
+EPUBJS.Book.prototype.gotoHref = function(url, defer){
+ var split, chapter, section, relativeURL, spinePos;
+ var deferred = defer || new RSVP.defer();
+
+ if(!this.isRendered) {
+ this.settings.goto = url;
+ return false;
+ }
+
+ // Currently going to a chapter
+ if(this._moving || this._rendering) {
+ this._gotoQ.enqueue("gotoHref", [url, deferred]);
+ return false;
+ }
+
+ split = url.split("#");
+ chapter = split[0];
+ section = split[1] || false;
+ if (chapter.search("://") == -1) {
+ relativeURL = chapter.replace(EPUBJS.core.uri(this.settings.contentsPath).path, '');
+ } else {
+ relativeURL = chapter.replace(this.settings.contentsPath, '');
+ }
+ spinePos = this.spineIndexByURL[relativeURL];
+
+ //-- If link fragment only stay on current chapter
+ if(!chapter){
+ spinePos = this.currentChapter ? this.currentChapter.spinePos : 0;
+ }
+
+ //-- Check that URL is present in the index, or stop
+ if(typeof(spinePos) != "number") return false;
+
+ if(!this.currentChapter || spinePos != this.currentChapter.spinePos){
+ //-- Load new chapter if different than current
+ return this.displayChapter(spinePos).then(function(){
+ if(section){
+ this.renderer.section(section);
+ }
+ deferred.resolve(this.renderer.currentLocationCfi);
+ }.bind(this));
+ }else{
+ //-- Goto section
+ if(section) {
+ this.renderer.section(section);
+ } else {
+ // Or jump to the start
+ this.renderer.firstPage();
+ }
+ deferred.resolve(this.renderer.currentLocationCfi);
+ }
+
+ deferred.promise.then(function(){
+ this._gotoQ.dequeue();
+ }.bind(this));
+
+ return deferred.promise;
+};
+
+EPUBJS.Book.prototype.gotoPage = function(pg){
+ var cfi = this.pagination.cfiFromPage(pg);
+ return this.gotoCfi(cfi);
+};
+
+EPUBJS.Book.prototype.gotoPercentage = function(percent){
+ var pg = this.pagination.pageFromPercentage(percent);
+ return this.gotoPage(pg);
+};
+
+EPUBJS.Book.prototype.preloadNextChapter = function() {
+ var next;
+ var chap = this.spinePos + 1;
+
+ if(chap >= this.spine.length){
+ return false;
+ }
+
+ next = new EPUBJS.Chapter(this.spine[chap]);
+ if(next) {
+ EPUBJS.core.request(next.absolute);
+ }
+};
+
+EPUBJS.Book.prototype.storeOffline = function() {
+ var book = this,
+ assets = EPUBJS.core.values(this.manifest);
+
+ //-- Creates a queue of all items to load
+ return this.store.put(assets).
+ then(function(){
+ book.settings.stored = true;
+ book.trigger("book:stored");
+ });
+};
+
+EPUBJS.Book.prototype.availableOffline = function() {
+ return this.settings.stored > 0 ? true : false;
+};
+
+EPUBJS.Book.prototype.toStorage = function () {
+ var key = this.settings.bookKey;
+ this.store.isStored(key).then(function(stored) {
+
+ if (stored === true) {
+ this.settings.stored = true;
+ return true;
+ }
+
+ return this.storeOffline()
+ .then(function() {
+ this.store.token(key, true);
+ }.bind(this));
+
+ }.bind(this));
+
+};
+EPUBJS.Book.prototype.fromStorage = function(stored) {
+ var hooks = [
+ EPUBJS.replace.head,
+ EPUBJS.replace.resources,
+ EPUBJS.replace.svg
+ ];
+
+ if(this.contained || this.settings.contained) return;
+
+ //-- If there is network connection, store the books contents
+ if(this.online){
+ this.opened.then(this.toStorage.bind(this));
+ }
+
+ if(this.store && this.settings.fromStorage && stored === false){
+ this.settings.fromStorage = false;
+ this.store.off("offline");
+ // this.renderer.removeHook("beforeChapterRender", hooks, true);
+ this.store = false;
+ }else if(!this.settings.fromStorage){
+
+ this.store = new EPUBJS.Storage(this.settings.credentials);
+ this.store.on("offline", function (offline) {
+ if (!offline) {
+ // Online
+ this.offline = false;
+ this.settings.fromStorage = false;
+ // this.renderer.removeHook("beforeChapterRender", hooks, true);
+ this.trigger("book:online");
+ } else {
+ // Offline
+ this.offline = true;
+ this.settings.fromStorage = true;
+ // this.renderer.registerHook("beforeChapterRender", hooks, true);
+ this.trigger("book:offline");
+ }
+ }.bind(this));
+
+ }
+
+};
+
+EPUBJS.Book.prototype.setStyle = function(style, val, prefixed) {
+ var noreflow = ["color", "background", "background-color"];
+
+ if(!this.isRendered) return this._q.enqueue("setStyle", arguments);
+
+ this.settings.styles[style] = val;
+
+ this.renderer.setStyle(style, val, prefixed);
+
+ if(noreflow.indexOf(style) === -1) {
+ // clearTimeout(this.reformatTimeout);
+ // this.reformatTimeout = setTimeout(function(){
+ this.renderer.reformat();
+ // }.bind(this), 10);
+ }
+};
+
+EPUBJS.Book.prototype.removeStyle = function(style) {
+ if(!this.isRendered) return this._q.enqueue("removeStyle", arguments);
+ this.renderer.removeStyle(style);
+ this.renderer.reformat();
+ delete this.settings.styles[style];
+};
+
+EPUBJS.Book.prototype.addHeadTag = function(tag, attrs) {
+ if(!this.isRendered) return this._q.enqueue("addHeadTag", arguments);
+ this.settings.headTags[tag] = attrs;
+};
+
+EPUBJS.Book.prototype.useSpreads = function(use) {
+ console.warn("useSpreads is deprecated, use forceSingle or set a layoutOveride instead");
+ if(use === false) {
+ this.forceSingle(true);
+ } else {
+ this.forceSingle(false);
+ }
+};
+
+EPUBJS.Book.prototype.forceSingle = function(_use) {
+ var force = typeof _use === "undefined" ? true : _use;
+
+ this.renderer.forceSingle(force);
+ this.settings.forceSingle = force;
+ if(this.isRendered) {
+ this.renderer.reformat();
+ }
+};
+
+EPUBJS.Book.prototype.setMinSpreadWidth = function(width) {
+ this.settings.minSpreadWidth = width;
+ if(this.isRendered) {
+ this.renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
+ this.renderer.reformat();
+ }
+};
+
+EPUBJS.Book.prototype.setGap = function(gap) {
+ this.settings.gap = gap;
+ if(this.isRendered) {
+ this.renderer.setGap(this.settings.gap);
+ this.renderer.reformat();
+ }
+};
+
+EPUBJS.Book.prototype.chapter = function(path) {
+ var spinePos = this.spineIndexByURL[path];
+ var spineItem;
+ var chapter;
+
+ if(spinePos){
+ spineItem = this.spine[spinePos];
+ chapter = new EPUBJS.Chapter(spineItem, this.store, this.settings.withCredentials);
+ chapter.load();
+ }
+ return chapter;
+};
+
+EPUBJS.Book.prototype.unload = function(){
+
+ if(this.settings.restore && localStorage) {
+ this.saveContents();
+ }
+
+ this.unlistenToRenderer(this.renderer);
+
+ this.trigger("book:unload");
+};
+
+EPUBJS.Book.prototype.destroy = function() {
+
+ window.removeEventListener("beforeunload", this.unload);
+
+ if(this.currentChapter) this.currentChapter.unload();
+
+ this.unload();
+
+ if(this.renderer) this.renderer.remove();
+
+};
+
+EPUBJS.Book.prototype._ready = function() {
+
+ this.trigger("book:ready");
+
+};
+
+EPUBJS.Book.prototype._rendered = function(err) {
+ var book = this;
+
+ this.isRendered = true;
+ this.trigger("book:rendered");
+
+ this._q.flush();
+};
+
+
+EPUBJS.Book.prototype.applyStyles = function(renderer, callback){
+ // if(!this.isRendered) return this._q.enqueue("applyStyles", arguments);
+ renderer.applyStyles(this.settings.styles);
+ callback();
+};
+
+EPUBJS.Book.prototype.applyHeadTags = function(renderer, callback){
+ // if(!this.isRendered) return this._q.enqueue("applyHeadTags", arguments);
+ renderer.applyHeadTags(this.settings.headTags);
+ callback();
+};
+
+EPUBJS.Book.prototype._registerReplacements = function(renderer){
+ renderer.registerHook("beforeChapterDisplay", this.applyStyles.bind(this, renderer), true);
+ renderer.registerHook("beforeChapterDisplay", this.applyHeadTags.bind(this, renderer), true);
+ renderer.registerHook("beforeChapterDisplay", EPUBJS.replace.hrefs.bind(this), true);
+};
+
+EPUBJS.Book.prototype._needsAssetReplacement = function(){
+ if(this.settings.fromStorage) {
+
+ //-- Filesystem api links are relative, so no need to replace them
+ // if(this.storage.getStorageType() == "filesystem") {
+ // return false;
+ // }
+
+ return true;
+
+ } else if(this.settings.contained) {
+
+ return true;
+
+ } else {
+
+ return false;
+
+ }
+};
+
+
+//-- http://www.idpf.org/epub/fxl/
+EPUBJS.Book.prototype.parseLayoutProperties = function(metadata){
+ var layout = (this.settings.layoutOveride && this.settings.layoutOveride.layout) || metadata.layout || "reflowable";
+ var spread = (this.settings.layoutOveride && this.settings.layoutOveride.spread) || metadata.spread || "auto";
+ var orientation = (this.settings.layoutOveride && this.settings.layoutOveride.orientation) || metadata.orientation || "auto";
+ return {
+ layout : layout,
+ spread : spread,
+ orientation : orientation
+ };
+};
+
+//-- Enable binding events to book
+RSVP.EventTarget.mixin(EPUBJS.Book.prototype);
+
+//-- Handle RSVP Errors
+RSVP.on('error', function(event) {
+ console.error(event);
+});
+
+// RSVP.configure('instrument', true); //-- true | will logging out all RSVP rejections
+// RSVP.on('created', listener);
+// RSVP.on('chained', listener);
+// RSVP.on('fulfilled', listener);
+// RSVP.on('rejected', function(event){
+// console.error(event.detail.message, event.detail.stack);
+// });
+
+EPUBJS.Chapter = function(spineObject, store, credentials){
+ this.href = spineObject.href;
+ this.absolute = spineObject.url;
+ this.id = spineObject.id;
+ this.spinePos = spineObject.index;
+ this.cfiBase = spineObject.cfiBase;
+ this.properties = spineObject.properties;
+ this.manifestProperties = spineObject.manifestProperties;
+ this.linear = spineObject.linear;
+ this.pages = 1;
+ this.store = store;
+ this.credentials = credentials;
+ this.epubcfi = new EPUBJS.EpubCFI();
+ this.deferred = new RSVP.defer();
+ this.loaded = this.deferred.promise;
+
+ EPUBJS.Hooks.mixin(this);
+ //-- Get pre-registered hooks for events
+ this.getHooks("beforeChapterRender");
+
+ // Cached for replacement urls from storage
+ this.caches = {};
+};
+
+
+EPUBJS.Chapter.prototype.load = function(_store, _credentials){
+ var store = _store || this.store;
+ var credentials = _credentials || this.credentials;
+ var promise;
+ // if(this.store && (!this.book.online || this.book.contained))
+ if(store){
+ promise = store.getXml(this.absolute);
+ }else{
+ promise = EPUBJS.core.request(this.absolute, false, credentials);
+ }
+
+ promise.then(function(xml){
+ try {
+ this.setDocument(xml);
+ this.deferred.resolve(this);
+ } catch (error) {
+ this.deferred.reject({
+ message : this.absolute + " -> " + error.message,
+ stack : new Error().stack
+ });
+ }
+ }.bind(this));
+
+ return promise;
+};
+
+EPUBJS.Chapter.prototype.render = function(_store){
+
+ return this.load().then(function(doc){
+
+ var head = doc.querySelector('head');
+ var base = doc.createElement("base");
+
+ base.setAttribute("href", this.absolute);
+ head.insertBefore(base, head.firstChild);
+
+ this.contents = doc;
+
+ return new RSVP.Promise(function (resolve, reject) {
+ this.triggerHooks("beforeChapterRender", function () {
+ resolve(doc);
+ }.bind(this), this);
+ }.bind(this));
+
+ }.bind(this))
+ .then(function(doc) {
+ var serializer = new XMLSerializer();
+ var contents = serializer.serializeToString(doc);
+ return contents;
+ }.bind(this));
+};
+
+EPUBJS.Chapter.prototype.url = function(_store){
+ var deferred = new RSVP.defer();
+ var store = _store || this.store;
+ var loaded;
+ var chapter = this;
+ var url;
+
+ if(store){
+ if(!this.tempUrl) {
+ store.getUrl(this.absolute).then(function(url){
+ chapter.tempUrl = url;
+ deferred.resolve(url);
+ });
+ } else {
+ url = this.tempUrl;
+ deferred.resolve(url);
+ }
+ }else{
+ url = this.absolute;
+ deferred.resolve(url);
+ }
+
+ return deferred.promise;
+};
+
+EPUBJS.Chapter.prototype.setPages = function(num){
+ this.pages = num;
+};
+
+EPUBJS.Chapter.prototype.getPages = function(num){
+ return this.pages;
+};
+
+EPUBJS.Chapter.prototype.getID = function(){
+ return this.ID;
+};
+
+EPUBJS.Chapter.prototype.unload = function(store){
+ this.document = null;
+ if(this.tempUrl && store) {
+ store.revokeUrl(this.tempUrl);
+ this.tempUrl = false;
+ }
+};
+
+EPUBJS.Chapter.prototype.setDocument = function(_document){
+ // var uri = _document.namespaceURI;
+ // var doctype = _document.doctype;
+ //
+ // // Creates an empty document
+ // this.document = _document.implementation.createDocument(
+ // uri,
+ // null,
+ // null
+ // );
+ // this.contents = this.document.importNode(
+ // _document.documentElement, //node to import
+ // true //clone its descendants
+ // );
+ //
+ // this.document.appendChild(this.contents);
+ this.document = _document;
+ this.contents = _document.documentElement;
+
+ // Fix to apply wgxpath to new document in IE
+ if(!this.document.evaluate && document.evaluate) {
+ this.document.evaluate = document.evaluate;
+ }
+
+ // this.deferred.resolve(this.contents);
+};
+
+EPUBJS.Chapter.prototype.cfiFromRange = function(_range) {
+ var range;
+ var startXpath, endXpath;
+ var startContainer, endContainer;
+ var cleanTextContent, cleanEndTextContent;
+
+ // Check for Contents
+ if(!this.document) return;
+
+ if(typeof document.evaluate != 'undefined') {
+
+ startXpath = EPUBJS.core.getElementXPath(_range.startContainer);
+ // console.log(startContainer)
+ endXpath = EPUBJS.core.getElementXPath(_range.endContainer);
+
+ startContainer = this.document.evaluate(startXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+
+ if(!_range.collapsed) {
+ endContainer = this.document.evaluate(endXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ }
+
+ range = this.document.createRange();
+ // Find Exact Range in original document
+ if(startContainer) {
+ try {
+ range.setStart(startContainer, _range.startOffset);
+ if(!_range.collapsed && endContainer) {
+ range.setEnd(endContainer, _range.endOffset);
+ }
+ } catch (e) {
+ console.log("missed");
+ startContainer = false;
+ }
+
+ }
+
+ // Fuzzy Match
+ if(!startContainer) {
+ console.log("not found, try fuzzy match");
+ cleanStartTextContent = EPUBJS.core.cleanStringForXpath(_range.startContainer.textContent);
+ startXpath = "//text()[contains(.," + cleanStartTextContent + ")]";
+
+ startContainer = this.document.evaluate(startXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+
+ if(startContainer){
+ // console.log("Found with Fuzzy");
+ range.setStart(startContainer, _range.startOffset);
+
+ if(!_range.collapsed) {
+ cleanEndTextContent = EPUBJS.core.cleanStringForXpath(_range.endContainer.textContent);
+ endXpath = "//text()[contains(.," + cleanEndTextContent + ")]";
+ endContainer = this.document.evaluate(endXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ if(endContainer) {
+ range.setEnd(endContainer, _range.endOffset);
+ }
+ }
+
+ }
+ }
+ } else {
+ range = _range; // Just evaluate the current documents range
+ }
+
+ // Generate the Cfi
+ return this.epubcfi.generateCfiFromRange(range, this.cfiBase);
+};
+
+EPUBJS.Chapter.prototype.find = function(_query){
+ var chapter = this;
+ var matches = [];
+ var query = _query.toLowerCase();
+ //var xpath = this.document.evaluate(".//text()[contains(translate(., '"+query.toUpperCase()+"', '"+query+"'),'"+query+"')]", this.document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ var find = function(node){
+ // Search String
+ var text = node.textContent.toLowerCase();
+ var range = chapter.document.createRange();
+ var cfi;
+ var pos;
+ var last = -1;
+ var excerpt;
+ var limit = 150;
+
+ while (pos != -1) {
+ pos = text.indexOf(query, last + 1);
+
+ if(pos != -1) {
+ // If Found, Create Range
+ range = chapter.document.createRange();
+ range.setStart(node, pos);
+ range.setEnd(node, pos + query.length);
+
+ //Generate CFI
+ cfi = chapter.cfiFromRange(range);
+
+ // Generate Excerpt
+ if(node.textContent.length < limit) {
+ excerpt = node.textContent;
+ } else {
+ excerpt = node.textContent.substring(pos-limit/2,pos+limit/2);
+ excerpt = "..." + excerpt + "...";
+ }
+
+ //Add CFI to list
+ matches.push({
+ cfi: cfi,
+ excerpt: excerpt
+ });
+ }
+
+ last = pos;
+ }
+
+ };
+
+ // Grab text nodes
+
+ /*
+ for ( var i=0 ; i < xpath.snapshotLength; i++ ) {
+ find(xpath.snapshotItem(i));
+ }
+ */
+
+ this.textSprint(this.document, function(node){
+ find(node);
+ });
+
+
+ // Return List of CFIs
+ return matches;
+};
+
+
+EPUBJS.Chapter.prototype.textSprint = function(root, func) {
+ var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
+ acceptNode: function (node) {
+ if (node.data && ! /^\s*$/.test(node.data) ) {
+ return NodeFilter.FILTER_ACCEPT;
+ } else {
+ return NodeFilter.FILTER_REJECT;
+ }
+ }
+ }, false);
+ var node;
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+
+};
+
+EPUBJS.Chapter.prototype.replace = function(query, func, finished, progress){
+ var items = this.contents.querySelectorAll(query),
+ resources = Array.prototype.slice.call(items),
+ count = resources.length;
+
+
+ if(count === 0) {
+ finished(false);
+ return;
+ }
+ resources.forEach(function(item){
+ var called = false;
+ var after = function(result, full){
+ if(called === false) {
+ count--;
+ if(progress) progress(result, full, count);
+ if(count <= 0 && finished) finished(true);
+ called = true;
+ }
+ };
+
+ func(item, after);
+
+ }.bind(this));
+
+};
+
+EPUBJS.Chapter.prototype.replaceWithStored = function(query, attr, func, callback) {
+ var _oldUrls,
+ _newUrls = {},
+ _store = this.store,
+ _cache = this.caches[query],
+ _uri = EPUBJS.core.uri(this.absolute),
+ _chapterBase = _uri.base,
+ _attr = attr,
+ _wait = 5,
+ progress = function(url, full, count) {
+ _newUrls[full] = url;
+ },
+ finished = function(notempty) {
+ if(callback) callback();
+ EPUBJS.core.values(_oldUrls).forEach(function(url){
+ _store.revokeUrl(url);
+ });
+
+ _cache = _newUrls;
+ };
+
+ if(!_store) return;
+
+ if(!_cache) _cache = {};
+ _oldUrls = EPUBJS.core.clone(_cache);
+
+ this.replace(query, function(link, done){
+ var src = link.getAttribute(_attr),
+ full = EPUBJS.core.resolveUrl(_chapterBase, src);
+
+ var replaceUrl = function(url) {
+ var timeout;
+ link.onload = function(){
+ clearTimeout(timeout);
+ done(url, full);
+ };
+
+ /*
+ link.onerror = function(e){
+ clearTimeout(timeout);
+ done(url, full);
+ console.error(e);
+ };
+ */
+
+ if(query == "svg image") {
+ //-- SVG needs this to trigger a load event
+ link.setAttribute("externalResourcesRequired", "true");
+ }
+
+ if(query == "link[href]" && link.getAttribute("rel") !== "stylesheet") {
+ //-- Only Stylesheet links seem to have a load events, just continue others
+ done(url, full);
+ } else {
+ timeout = setTimeout(function(){
+ done(url, full);
+ }, _wait);
+ }
+
+ if (url) {
+ link.setAttribute(_attr, url);
+ }
+
+ };
+
+ if(full in _oldUrls){
+ replaceUrl(_oldUrls[full]);
+ _newUrls[full] = _oldUrls[full];
+ delete _oldUrls[full];
+ }else{
+ func(_store, full, replaceUrl, link);
+ }
+
+ }, finished, progress);
+};
+
+var EPUBJS = EPUBJS || {};
+EPUBJS.core = {};
+
+//-- Get a element for an id
+EPUBJS.core.getEl = function(elem) {
+ return document.getElementById(elem);
+};
+
+//-- Get all elements for a class
+EPUBJS.core.getEls = function(classes) {
+ return document.getElementsByClassName(classes);
+};
+
+EPUBJS.core.request = function(url, type, withCredentials) {
+ var supportsURL = window.URL;
+ var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
+ var deferred = new RSVP.defer();
+ var xhr = new XMLHttpRequest();
+ var uri;
+
+ //-- Check from PDF.js:
+ // https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
+ var xhrPrototype = XMLHttpRequest.prototype;
+
+ var handler = function() {
+ var r;
+
+ if (this.readyState != this.DONE) return;
+
+ if ((this.status === 200 || this.status === 0) && this.response) { // Android & Firefox reporting 0 for local & blob urls
+ if (type == 'xml'){
+ // If this.responseXML wasn't set, try to parse using a DOMParser from text
+ if(!this.responseXML) {
+ r = new DOMParser().parseFromString(this.response, "application/xml");
+ } else {
+ r = this.responseXML;
+ }
+ } else if (type == 'xhtml') {
+ if (!this.responseXML){
+ r = new DOMParser().parseFromString(this.response, "application/xhtml+xml");
+ } else {
+ r = this.responseXML;
+ }
+ } else if (type == 'html') {
+ if (!this.responseXML){
+ r = new DOMParser().parseFromString(this.response, "text/html");
+ } else {
+ r = this.responseXML;
+ }
+ } else if (type == 'json') {
+ r = JSON.parse(this.response);
+ } else if (type == 'blob') {
+ if (supportsURL) {
+ r = this.response;
+ } else {
+ //-- Safari doesn't support responseType blob, so create a blob from arraybuffer
+ r = new Blob([this.response]);
+ }
+ } else {
+ r = this.response;
+ }
+
+ deferred.resolve(r);
+ } else {
+ deferred.reject({
+ message : this.response,
+ stack : new Error().stack
+ });
+ }
+ };
+
+ if (!('overrideMimeType' in xhrPrototype)) {
+ // IE10 might have response, but not overrideMimeType
+ Object.defineProperty(xhrPrototype, 'overrideMimeType', {
+ value: function xmlHttpRequestOverrideMimeType(mimeType) {}
+ });
+ }
+
+ xhr.onreadystatechange = handler;
+ xhr.open("GET", url, true);
+
+ if(withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ // If type isn't set, determine it from the file extension
+ if(!type) {
+ uri = EPUBJS.core.uri(url);
+ type = uri.extension;
+ type = {
+ 'htm': 'html'
+ }[type] || type;
+ }
+
+ if(type == 'blob'){
+ xhr.responseType = BLOB_RESPONSE;
+ }
+
+ if(type == "json") {
+ xhr.setRequestHeader("Accept", "application/json");
+ }
+
+ if(type == 'xml') {
+ xhr.responseType = "document";
+ xhr.overrideMimeType('text/xml'); // for OPF parsing
+ }
+
+ if(type == 'xhtml') {
+ xhr.responseType = "document";
+ }
+
+ if(type == 'html') {
+ xhr.responseType = "document";
+ }
+
+ if(type == "binary") {
+ xhr.responseType = "arraybuffer";
+ }
+
+ xhr.send();
+
+ return deferred.promise;
+};
+
+EPUBJS.core.toArray = function(obj) {
+ var arr = [];
+
+ for (var member in obj) {
+ var newitm;
+ if ( obj.hasOwnProperty(member) ) {
+ newitm = obj[member];
+ newitm.ident = member;
+ arr.push(newitm);
+ }
+ }
+
+ return arr;
+};
+
+//-- Parse the different parts of a url, returning a object
+EPUBJS.core.uri = function(url){
+ var uri = {
+ protocol : '',
+ host : '',
+ path : '',
+ origin : '',
+ directory : '',
+ base : '',
+ filename : '',
+ extension : '',
+ fragment : '',
+ href : url
+ },
+ blob = url.indexOf('blob:'),
+ doubleSlash = url.indexOf('://'),
+ search = url.indexOf('?'),
+ fragment = url.indexOf("#"),
+ withoutProtocol,
+ dot,
+ firstSlash;
+
+ if(blob === 0) {
+ uri.protocol = "blob";
+ uri.base = url.indexOf(0, fragment);
+ return uri;
+ }
+
+ if(fragment != -1) {
+ uri.fragment = url.slice(fragment + 1);
+ url = url.slice(0, fragment);
+ }
+
+ if(search != -1) {
+ uri.search = url.slice(search + 1);
+ url = url.slice(0, search);
+ href = url;
+ }
+
+ if(doubleSlash != -1) {
+ uri.protocol = url.slice(0, doubleSlash);
+ withoutProtocol = url.slice(doubleSlash+3);
+ firstSlash = withoutProtocol.indexOf('/');
+
+ if(firstSlash === -1) {
+ uri.host = uri.path;
+ uri.path = "";
+ } else {
+ uri.host = withoutProtocol.slice(0, firstSlash);
+ uri.path = withoutProtocol.slice(firstSlash);
+ }
+
+
+ uri.origin = uri.protocol + "://" + uri.host;
+
+ uri.directory = EPUBJS.core.folder(uri.path);
+
+ uri.base = uri.origin + uri.directory;
+ // return origin;
+ } else {
+ uri.path = url;
+ uri.directory = EPUBJS.core.folder(url);
+ uri.base = uri.directory;
+ }
+
+ //-- Filename
+ uri.filename = url.replace(uri.base, '');
+ dot = uri.filename.lastIndexOf('.');
+ if(dot != -1) {
+ uri.extension = uri.filename.slice(dot+1);
+ }
+ return uri;
+};
+
+//-- Parse out the folder, will return everything before the last slash
+
+EPUBJS.core.folder = function(url){
+
+ var lastSlash = url.lastIndexOf('/');
+
+ if(lastSlash == -1) var folder = '';
+
+ folder = url.slice(0, lastSlash + 1);
+
+ return folder;
+
+};
+
+//-- https://github.com/ebidel/filer.js/blob/master/src/filer.js#L128
+EPUBJS.core.dataURLToBlob = function(dataURL) {
+ var BASE64_MARKER = ';base64,',
+ parts, contentType, raw, rawLength, uInt8Array;
+
+ if (dataURL.indexOf(BASE64_MARKER) == -1) {
+ parts = dataURL.split(',');
+ contentType = parts[0].split(':')[1];
+ raw = parts[1];
+
+ return new Blob([raw], {type: contentType});
+ }
+
+ parts = dataURL.split(BASE64_MARKER);
+ contentType = parts[0].split(':')[1];
+ raw = window.atob(parts[1]);
+ rawLength = raw.length;
+
+ uInt8Array = new Uint8Array(rawLength);
+
+ for (var i = 0; i < rawLength; ++i) {
+ uInt8Array[i] = raw.charCodeAt(i);
+ }
+
+ return new Blob([uInt8Array], {type: contentType});
+};
+
+//-- Load scripts async: http://stackoverflow.com/questions/7718935/load-scripts-asynchronously
+EPUBJS.core.addScript = function(src, callback, target) {
+ var s, r;
+ r = false;
+ s = document.createElement('script');
+ s.type = 'text/javascript';
+ s.async = false;
+ s.src = src;
+ s.onload = s.onreadystatechange = function() {
+ if ( !r && (!this.readyState || this.readyState == 'complete') ) {
+ r = true;
+ if(callback) callback();
+ }
+ };
+ target = target || document.body;
+ target.appendChild(s);
+};
+
+EPUBJS.core.addScripts = function(srcArr, callback, target) {
+ var total = srcArr.length,
+ curr = 0,
+ cb = function(){
+ curr++;
+ if(total == curr){
+ if(callback) callback();
+ }else{
+ EPUBJS.core.addScript(srcArr[curr], cb, target);
+ }
+ };
+
+ EPUBJS.core.addScript(srcArr[curr], cb, target);
+};
+
+EPUBJS.core.addCss = function(src, callback, target) {
+ var s, r;
+ r = false;
+ s = document.createElement('link');
+ s.type = 'text/css';
+ s.rel = "stylesheet";
+ s.href = src;
+ s.onload = s.onreadystatechange = function() {
+ if ( !r && (!this.readyState || this.readyState == 'complete') ) {
+ r = true;
+ if(callback) callback();
+ }
+ };
+ target = target || document.body;
+ target.appendChild(s);
+};
+
+EPUBJS.core.prefixed = function(unprefixed) {
+ var vendors = ["Webkit", "Moz", "O", "ms" ],
+ prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
+ upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
+ length = vendors.length;
+
+ if (typeof(document.documentElement.style[unprefixed]) != 'undefined') {
+ return unprefixed;
+ }
+
+ for ( var i=0; i < length; i++ ) {
+ if (typeof(document.documentElement.style[vendors[i] + upper]) != 'undefined') {
+ return vendors[i] + upper;
+ }
+ }
+
+ return unprefixed;
+};
+
+EPUBJS.core.resolveUrl = function(base, path) {
+ var url,
+ segments = [],
+ uri = EPUBJS.core.uri(path),
+ folders = base.split("/"),
+ paths;
+
+ if(uri.host) {
+ return path;
+ }
+
+ folders.pop();
+
+ paths = path.split("/");
+ paths.forEach(function(p){
+ if(p === ".."){
+ folders.pop();
+ }else{
+ segments.push(p);
+ }
+ });
+
+ url = folders.concat(segments);
+
+ return url.join("/");
+};
+
+// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
+EPUBJS.core.uuid = function() {
+ var d = new Date().getTime();
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = (d + Math.random()*16)%16 | 0;
+ d = Math.floor(d/16);
+ return (c=='x' ? r : (r&0x7|0x8)).toString(16);
+ });
+ return uuid;
+};
+
+// Fast quicksort insert for sorted array -- based on:
+// http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
+EPUBJS.core.insert = function(item, array, compareFunction) {
+ var location = EPUBJS.core.locationOf(item, array, compareFunction);
+ array.splice(location, 0, item);
+
+ return location;
+};
+
+EPUBJS.core.locationOf = function(item, array, compareFunction, _start, _end) {
+ var start = _start || 0;
+ var end = _end || array.length;
+ var pivot = parseInt(start + (end - start) / 2);
+ var compared;
+ if(!compareFunction){
+ compareFunction = function(a, b) {
+ if(a > b) return 1;
+ if(a < b) return -1;
+ if(a = b) return 0;
+ };
+ }
+ if(end-start <= 0) {
+ return pivot;
+ }
+
+ compared = compareFunction(array[pivot], item);
+ if(end-start === 1) {
+ return compared > 0 ? pivot : pivot + 1;
+ }
+
+ if(compared === 0) {
+ return pivot;
+ }
+ if(compared === -1) {
+ return EPUBJS.core.locationOf(item, array, compareFunction, pivot, end);
+ } else{
+ return EPUBJS.core.locationOf(item, array, compareFunction, start, pivot);
+ }
+};
+
+EPUBJS.core.indexOfSorted = function(item, array, compareFunction, _start, _end) {
+ var start = _start || 0;
+ var end = _end || array.length;
+ var pivot = parseInt(start + (end - start) / 2);
+ var compared;
+ if(!compareFunction){
+ compareFunction = function(a, b) {
+ if(a > b) return 1;
+ if(a < b) return -1;
+ if(a = b) return 0;
+ };
+ }
+ if(end-start <= 0) {
+ return -1; // Not found
+ }
+
+ compared = compareFunction(array[pivot], item);
+ if(end-start === 1) {
+ return compared === 0 ? pivot : -1;
+ }
+ if(compared === 0) {
+ return pivot; // Found
+ }
+ if(compared === -1) {
+ return EPUBJS.core.indexOfSorted(item, array, compareFunction, pivot, end);
+ } else{
+ return EPUBJS.core.indexOfSorted(item, array, compareFunction, start, pivot);
+ }
+};
+
+
+EPUBJS.core.queue = function(_scope){
+ var _q = [];
+ var scope = _scope;
+ // Add an item to the queue
+ var enqueue = function(funcName, args, context) {
+ _q.push({
+ "funcName" : funcName,
+ "args" : args,
+ "context" : context
+ });
+ return _q;
+ };
+ // Run one item
+ var dequeue = function(){
+ var inwait;
+ if(_q.length) {
+ inwait = _q.shift();
+ // Defer to any current tasks
+ // setTimeout(function(){
+ scope[inwait.funcName].apply(inwait.context || scope, inwait.args);
+ // }, 0);
+ }
+ };
+
+ // Run All
+ var flush = function(){
+ while(_q.length) {
+ dequeue();
+ }
+ };
+ // Clear all items in wait
+ var clear = function(){
+ _q = [];
+ };
+
+ var length = function(){
+ return _q.length;
+ };
+
+ return {
+ "enqueue" : enqueue,
+ "dequeue" : dequeue,
+ "flush" : flush,
+ "clear" : clear,
+ "length" : length
+ };
+};
+
+// From: https://code.google.com/p/fbug/source/browse/branches/firebug1.10/content/firebug/lib/xpath.js
+/**
+ * Gets an XPath for an element which describes its hierarchical location.
+ */
+EPUBJS.core.getElementXPath = function(element) {
+ if (element && element.id) {
+ return '//*[@id="' + element.id + '"]';
+ } else {
+ return EPUBJS.core.getElementTreeXPath(element);
+ }
+};
+
+EPUBJS.core.getElementTreeXPath = function(element) {
+ var paths = [];
+ var isXhtml = (element.ownerDocument.documentElement.getAttribute('xmlns') === "http://www.w3.org/1999/xhtml");
+ var index, nodeName, tagName, pathIndex;
+
+ if(element.nodeType === Node.TEXT_NODE){
+ // index = Array.prototype.indexOf.call(element.parentNode.childNodes, element) + 1;
+ index = EPUBJS.core.indexOfTextNode(element) + 1;
+
+ paths.push("text()["+index+"]");
+ element = element.parentNode;
+ }
+
+ // Use nodeName (instead of localName) so namespace prefix is included (if any).
+ for (; element && element.nodeType == 1; element = element.parentNode)
+ {
+ index = 0;
+ for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling)
+ {
+ // Ignore document type declaration.
+ if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE) {
+ continue;
+ }
+ if (sibling.nodeName == element.nodeName) {
+ ++index;
+ }
+ }
+ nodeName = element.nodeName.toLowerCase();
+ tagName = (isXhtml ? "xhtml:" + nodeName : nodeName);
+ pathIndex = (index ? "[" + (index+1) + "]" : "");
+ paths.splice(0, 0, tagName + pathIndex);
+ }
+
+ return paths.length ? "./" + paths.join("/") : null;
+};
+
+EPUBJS.core.nsResolver = function(prefix) {
+ var ns = {
+ 'xhtml' : 'http://www.w3.org/1999/xhtml',
+ 'epub': 'http://www.idpf.org/2007/ops'
+ };
+ return ns[prefix] || null;
+};
+
+//https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
+EPUBJS.core.cleanStringForXpath = function(str) {
+ var parts = str.match(/[^'"]+|['"]/g);
+ parts = parts.map(function(part){
+ if (part === "'") {
+ return '\"\'\"'; // output "'"
+ }
+
+ if (part === '"') {
+ return "\'\"\'"; // output '"'
+ }
+ return "\'" + part + "\'";
+ });
+ return "concat(\'\'," + parts.join(",") + ")";
+};
+
+EPUBJS.core.indexOfTextNode = function(textNode){
+ var parent = textNode.parentNode;
+ var children = parent.childNodes;
+ var sib;
+ var index = -1;
+ for (var i = 0; i < children.length; i++) {
+ sib = children[i];
+ if(sib.nodeType === Node.TEXT_NODE){
+ index++;
+ }
+ if(sib == textNode) break;
+ }
+
+ return index;
+};
+
+// Underscore
+EPUBJS.core.defaults = function(obj) {
+ for (var i = 1, length = arguments.length; i < length; i++) {
+ var source = arguments[i];
+ for (var prop in source) {
+ if (obj[prop] === void 0) obj[prop] = source[prop];
+ }
+ }
+ return obj;
+};
+
+EPUBJS.core.extend = function(target) {
+ var sources = [].slice.call(arguments, 1);
+ sources.forEach(function (source) {
+ if(!source) return;
+ Object.getOwnPropertyNames(source).forEach(function(propName) {
+ Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
+ });
+ });
+ return target;
+};
+
+EPUBJS.core.clone = function(obj) {
+ return EPUBJS.core.isArray(obj) ? obj.slice() : EPUBJS.core.extend({}, obj);
+};
+
+EPUBJS.core.isElement = function(obj) {
+ return !!(obj && obj.nodeType == 1);
+};
+
+EPUBJS.core.isNumber = function(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+};
+
+EPUBJS.core.isString = function(str) {
+ return (typeof str === 'string' || str instanceof String);
+};
+
+EPUBJS.core.isArray = Array.isArray || function(obj) {
+ return Object.prototype.toString.call(obj) === '[object Array]';
+};
+
+// Lodash
+EPUBJS.core.values = function(object) {
+ var index = -1;
+ var props, length, result;
+
+ if(!object) return [];
+
+ props = Object.keys(object);
+ length = props.length;
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = object[props[index]];
+ }
+ return result;
+};
+
+EPUBJS.EpubCFI = function(cfiStr){
+ if(cfiStr) return this.parse(cfiStr);
+};
+
+EPUBJS.EpubCFI.prototype.generateChapterComponent = function(_spineNodeIndex, _pos, id) {
+ var pos = parseInt(_pos),
+ spineNodeIndex = _spineNodeIndex + 1,
+ cfi = '/'+spineNodeIndex+'/';
+
+ cfi += (pos + 1) * 2;
+
+ if(id) cfi += "[" + id + "]";
+
+ //cfi += "!";
+
+ return cfi;
+};
+
+EPUBJS.EpubCFI.prototype.generatePathComponent = function(steps) {
+ var parts = [];
+
+ steps.forEach(function(part){
+ var segment = '';
+ segment += (part.index + 1) * 2;
+
+ if(part.id) {
+ segment += "[" + part.id + "]";
+ }
+
+ parts.push(segment);
+ });
+
+ return parts.join('/');
+};
+
+EPUBJS.EpubCFI.prototype.generateCfiFromElement = function(element, chapter) {
+ var steps = this.pathTo(element);
+ var path = this.generatePathComponent(steps);
+ if(!path.length) {
+ // Start of Chapter
+ return "epubcfi(" + chapter + "!/4/)";
+ } else {
+ // First Text Node
+ return "epubcfi(" + chapter + "!" + path + "/1:0)";
+ }
+};
+
+EPUBJS.EpubCFI.prototype.pathTo = function(node) {
+ var stack = [],
+ children;
+
+ while(node && node.parentNode !== null && node.parentNode.nodeType != 9) {
+ children = node.parentNode.children;
+
+ stack.unshift({
+ 'id' : node.id,
+ // 'classList' : node.classList,
+ 'tagName' : node.tagName,
+ 'index' : children ? Array.prototype.indexOf.call(children, node) : 0
+ });
+
+ node = node.parentNode;
+ }
+
+ return stack;
+};
+
+EPUBJS.EpubCFI.prototype.getChapterComponent = function(cfiStr) {
+
+ var splitStr = cfiStr.split("!");
+
+ return splitStr[0];
+};
+
+EPUBJS.EpubCFI.prototype.getPathComponent = function(cfiStr) {
+
+ var splitStr = cfiStr.split("!");
+ var pathComponent = splitStr[1] ? splitStr[1].split(":") : '';
+
+ return pathComponent[0];
+};
+
+EPUBJS.EpubCFI.prototype.getCharecterOffsetComponent = // backwards-compat
+EPUBJS.EpubCFI.prototype.getCharacterOffsetComponent = function(cfiStr) {
+ var splitStr = cfiStr.split(":");
+ return splitStr[1] || '';
+};
+
+
+EPUBJS.EpubCFI.prototype.parse = function(cfiStr) {
+ var cfi = {},
+ chapSegment,
+ chapterComponent,
+ pathComponent,
+ characterOffsetComponent,
+ assertion,
+ chapId,
+ path,
+ end,
+ endInt,
+ text,
+ parseStep = function(part){
+ var type, index, has_brackets, id;
+
+ type = "element";
+ index = parseInt(part) / 2 - 1;
+ has_brackets = part.match(/\[(.*)\]/);
+ if(has_brackets && has_brackets[1]){
+ id = has_brackets[1];
+ }
+
+ return {
+ "type" : type,
+ 'index' : index,
+ 'id' : id || false
+ };
+ };
+
+ if(typeof cfiStr !== "string") {
+ return {spinePos: -1};
+ }
+
+ cfi.str = cfiStr;
+
+ if(cfiStr.indexOf("epubcfi(") === 0 && cfiStr[cfiStr.length-1] === ")") {
+ // Remove intial epubcfi( and ending )
+ cfiStr = cfiStr.slice(8, cfiStr.length-1);
+ }
+
+ chapterComponent = this.getChapterComponent(cfiStr);
+ pathComponent = this.getPathComponent(cfiStr) || '';
+ characterOffsetComponent = this.getCharacterOffsetComponent(cfiStr);
+ // Make sure this is a valid cfi or return
+ if(!chapterComponent) {
+ return {spinePos: -1};
+ }
+
+ // Chapter segment is always the second one
+ chapSegment = chapterComponent.split("/")[2] || '';
+ if(!chapSegment) return {spinePos:-1};
+
+ cfi.spinePos = (parseInt(chapSegment) / 2 - 1 ) || 0;
+
+ chapId = chapSegment.match(/\[(.*)\]/);
+
+ cfi.spineId = chapId ? chapId[1] : false;
+
+ if(pathComponent.indexOf(',') != -1) {
+ // Handle ranges -- not supported yet
+ console.warn("CFI Ranges are not supported");
+ }
+
+ path = pathComponent.split('/');
+ end = path.pop();
+
+ cfi.steps = [];
+
+ path.forEach(function(part){
+ var step;
+
+ if(part) {
+ step = parseStep(part);
+ cfi.steps.push(step);
+ }
+ });
+
+ //-- Check if END is a text node or element
+ endInt = parseInt(end);
+ if(!isNaN(endInt)) {
+
+ if(endInt % 2 === 0) { // Even = is an element
+ cfi.steps.push(parseStep(end));
+ } else {
+ cfi.steps.push({
+ "type" : "text",
+ 'index' : (endInt - 1 ) / 2
+ });
+ }
+
+ }
+
+ assertion = characterOffsetComponent.match(/\[(.*)\]/);
+ if(assertion && assertion[1]){
+ cfi.characterOffset = parseInt(characterOffsetComponent.split('[')[0]);
+ // We arent handling these assertions yet
+ cfi.textLocationAssertion = assertion[1];
+ } else {
+ cfi.characterOffset = parseInt(characterOffsetComponent);
+ }
+
+ return cfi;
+};
+
+EPUBJS.EpubCFI.prototype.addMarker = function(cfi, _doc, _marker) {
+ var doc = _doc || document;
+ var marker = _marker || this.createMarker(doc);
+ var parent;
+ var lastStep;
+ var text;
+ var split;
+
+ if(typeof cfi === 'string') {
+ cfi = this.parse(cfi);
+ }
+ // Get the terminal step
+ lastStep = cfi.steps[cfi.steps.length-1];
+
+ // check spinePos
+ if(cfi.spinePos === -1) {
+ // Not a valid CFI
+ return false;
+ }
+
+ // Find the CFI elements parent
+ parent = this.findParent(cfi, doc);
+
+ if(!parent) {
+ // CFI didn't return an element
+ // Maybe it isnt in the current chapter?
+ return false;
+ }
+
+ if(lastStep && lastStep.type === "text") {
+ text = parent.childNodes[lastStep.index];
+ if(cfi.characterOffset){
+ split = text.splitText(cfi.characterOffset);
+ marker.classList.add("EPUBJS-CFI-SPLIT");
+ parent.insertBefore(marker, split);
+ } else {
+ parent.insertBefore(marker, text);
+ }
+ } else {
+ parent.insertBefore(marker, parent.firstChild);
+ }
+
+ return marker;
+};
+
+EPUBJS.EpubCFI.prototype.createMarker = function(_doc) {
+ var doc = _doc || document;
+ var element = doc.createElement('span');
+ element.id = "EPUBJS-CFI-MARKER:"+ EPUBJS.core.uuid();
+ element.classList.add("EPUBJS-CFI-MARKER");
+
+ return element;
+};
+
+EPUBJS.EpubCFI.prototype.removeMarker = function(marker, _doc) {
+ var doc = _doc || document;
+ // var id = marker.id;
+
+ // Cleanup textnodes if they were split
+ if(marker.classList.contains("EPUBJS-CFI-SPLIT")){
+ nextSib = marker.nextSibling;
+ prevSib = marker.previousSibling;
+ if(nextSib &&
+ prevSib &&
+ nextSib.nodeType === 3 &&
+ prevSib.nodeType === 3){
+
+ prevSib.textContent += nextSib.textContent;
+ marker.parentNode.removeChild(nextSib);
+ }
+ marker.parentNode.removeChild(marker);
+ } else if(marker.classList.contains("EPUBJS-CFI-MARKER")) {
+ // Remove only elements added as markers
+ marker.parentNode.removeChild(marker);
+ }
+
+};
+
+EPUBJS.EpubCFI.prototype.findParent = function(cfi, _doc) {
+ var doc = _doc || document,
+ element = doc.getElementsByTagName('html')[0],
+ children = Array.prototype.slice.call(element.children),
+ num, index, part, sections,
+ text, textBegin, textEnd;
+
+ if(typeof cfi === 'string') {
+ cfi = this.parse(cfi);
+ }
+
+ sections = cfi.steps.slice(0); // Clone steps array
+ if(!sections.length) {
+ return doc.getElementsByTagName('body')[0];
+ }
+
+ while(sections && sections.length > 0) {
+ part = sections.shift();
+ // Find textNodes Parent
+ if(part.type === "text") {
+ text = element.childNodes[part.index];
+ element = text.parentNode || element;
+ // Find element by id if present
+ } else if(part.id){
+ element = doc.getElementById(part.id);
+ // Find element in parent
+ }else{
+ element = children[part.index];
+ }
+ // Element can't be found
+ if(!element || typeof element === "undefined") {
+ console.error("No Element For", part, cfi.str);
+ return false;
+ }
+ // Get current element children and continue through steps
+ children = Array.prototype.slice.call(element.children);
+ }
+
+ return element;
+};
+
+EPUBJS.EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
+ if(typeof cfiOne === 'string') {
+ cfiOne = new EPUBJS.EpubCFI(cfiOne);
+ }
+ if(typeof cfiTwo === 'string') {
+ cfiTwo = new EPUBJS.EpubCFI(cfiTwo);
+ }
+ // Compare Spine Positions
+ if(cfiOne.spinePos > cfiTwo.spinePos) {
+ return 1;
+ }
+ if(cfiOne.spinePos < cfiTwo.spinePos) {
+ return -1;
+ }
+
+
+ // Compare Each Step in the First item
+ for (var i = 0; i < cfiOne.steps.length; i++) {
+ if(!cfiTwo.steps[i]) {
+ return 1;
+ }
+ if(cfiOne.steps[i].index > cfiTwo.steps[i].index) {
+ return 1;
+ }
+ if(cfiOne.steps[i].index < cfiTwo.steps[i].index) {
+ return -1;
+ }
+ // Otherwise continue checking
+ }
+
+ // All steps in First present in Second
+ if(cfiOne.steps.length < cfiTwo.steps.length) {
+ return -1;
+ }
+
+ // Compare the character offset of the text node
+ if(cfiOne.characterOffset > cfiTwo.characterOffset) {
+ return 1;
+ }
+ if(cfiOne.characterOffset < cfiTwo.characterOffset) {
+ return -1;
+ }
+
+ // CFI's are equal
+ return 0;
+};
+
+EPUBJS.EpubCFI.prototype.generateCfiFromHref = function(href, book) {
+ var uri = EPUBJS.core.uri(href);
+ var path = uri.path;
+ var fragment = uri.fragment;
+ var spinePos = book.spineIndexByURL[path];
+ var loaded;
+ var deferred = new RSVP.defer();
+ var epubcfi = new EPUBJS.EpubCFI();
+ var spineItem;
+
+ if(typeof spinePos !== "undefined"){
+ spineItem = book.spine[spinePos];
+ loaded = book.loadXml(spineItem.url);
+ loaded.then(function(doc){
+ var element = doc.getElementById(fragment);
+ var cfi;
+ cfi = epubcfi.generateCfiFromElement(element, spineItem.cfiBase);
+ deferred.resolve(cfi);
+ });
+ }
+
+ return deferred.promise;
+};
+
+EPUBJS.EpubCFI.prototype.generateCfiFromTextNode = function(anchor, offset, base) {
+ var parent = anchor.parentNode;
+ var steps = this.pathTo(parent);
+ var path = this.generatePathComponent(steps);
+ var index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, anchor));
+ return "epubcfi(" + base + "!" + path + "/"+index+":"+(offset || 0)+")";
+};
+
+EPUBJS.EpubCFI.prototype.generateCfiFromRangeAnchor = function(range, base) {
+ var anchor = range.anchorNode;
+ var offset = range.anchorOffset;
+ return this.generateCfiFromTextNode(anchor, offset, base);
+};
+
+EPUBJS.EpubCFI.prototype.generateCfiFromRange = function(range, base) {
+ var start, startElement, startSteps, startPath, startOffset, startIndex;
+ var end, endElement, endSteps, endPath, endOffset, endIndex;
+
+ start = range.startContainer;
+
+ if(start.nodeType === 3) { // text node
+ startElement = start.parentNode;
+ //startIndex = 1 + (2 * Array.prototype.indexOf.call(startElement.childNodes, start));
+ startIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(start));
+ startSteps = this.pathTo(startElement);
+ } else if(range.collapsed) {
+ return this.generateCfiFromElement(start, base); // single element
+ } else {
+ startSteps = this.pathTo(start);
+ }
+
+ startPath = this.generatePathComponent(startSteps);
+ startOffset = range.startOffset;
+
+ if(!range.collapsed) {
+ end = range.endContainer;
+
+ if(end.nodeType === 3) { // text node
+ endElement = end.parentNode;
+ // endIndex = 1 + (2 * Array.prototype.indexOf.call(endElement.childNodes, end));
+ endIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(end));
+
+ endSteps = this.pathTo(endElement);
+ } else {
+ endSteps = this.pathTo(end);
+ }
+
+ endPath = this.generatePathComponent(endSteps);
+ endOffset = range.endOffset;
+
+ // Remove steps present in startPath
+ endPath = endPath.replace(startPath, '');
+
+ if (endPath.length) {
+ endPath = endPath + "/";
+ }
+
+ return "epubcfi(" + base + "!" + startPath + "/" + startIndex + ":" + startOffset + "," + endPath + endIndex + ":" + endOffset + ")";
+
+ } else {
+ return "epubcfi(" + base + "!" + startPath + "/"+ startIndex +":"+ startOffset +")";
+ }
+};
+
+EPUBJS.EpubCFI.prototype.generateXpathFromSteps = function(steps) {
+ var xpath = [".", "*"];
+
+ steps.forEach(function(step){
+ var position = step.index + 1;
+
+ if(step.id){
+ xpath.push("*[position()=" + position + " and @id='" + step.id + "']");
+ } else if(step.type === "text") {
+ xpath.push("text()[" + position + "]");
+ } else {
+ xpath.push("*[" + position + "]");
+ }
+ });
+
+ return xpath.join("/");
+};
+
+EPUBJS.EpubCFI.prototype.generateQueryFromSteps = function(steps) {
+ var query = ["html"];
+
+ steps.forEach(function(step){
+ var position = step.index + 1;
+
+ if(step.id){
+ query.push("#" + step.id);
+ } else if(step.type === "text") {
+ // unsupported in querySelector
+ // query.push("text()[" + position + "]");
+ } else {
+ query.push("*:nth-child(" + position + ")");
+ }
+ });
+
+ return query.join(">");
+};
+
+
+EPUBJS.EpubCFI.prototype.generateRangeFromCfi = function(cfi, _doc) {
+ var doc = _doc || document;
+ var range = doc.createRange();
+ var lastStep;
+ var xpath;
+ var startContainer;
+ var textLength;
+ var query;
+ var startContainerParent;
+
+ if(typeof cfi === 'string') {
+ cfi = this.parse(cfi);
+ }
+
+ // check spinePos
+ if(cfi.spinePos === -1) {
+ // Not a valid CFI
+ return false;
+ }
+
+ // Get the terminal step
+ lastStep = cfi.steps[cfi.steps.length-1];
+
+ if(typeof document.evaluate != 'undefined') {
+ xpath = this.generateXpathFromSteps(cfi.steps);
+ startContainer = doc.evaluate(xpath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ } else {
+ // Get the query string
+ query = this.generateQueryFromSteps(cfi.steps);
+ // Find the containing element
+ startContainerParent = doc.querySelector(query);
+ // Find the text node within that element
+ if(startContainerParent && lastStep.type == "text") {
+ startContainer = startContainerParent.childNodes[lastStep.index];
+ }
+ }
+
+ if(!startContainer) {
+ return null;
+ }
+
+ if(startContainer && cfi.characterOffset >= 0) {
+ textLength = startContainer.length;
+
+ if(cfi.characterOffset < textLength) {
+ range.setStart(startContainer, cfi.characterOffset);
+ range.setEnd(startContainer, textLength );
+ } else {
+ console.debug("offset greater than length:", cfi.characterOffset, textLength);
+ range.setStart(startContainer, textLength - 1 );
+ range.setEnd(startContainer, textLength );
+ }
+ } else if(startContainer) {
+ range.selectNode(startContainer);
+ }
+ // doc.defaultView.getSelection().addRange(range);
+ return range;
+};
+
+EPUBJS.EpubCFI.prototype.isCfiString = function(target) {
+ if(typeof target === "string" &&
+ target.indexOf("epubcfi(") === 0) {
+ return true;
+ }
+
+ return false;
+};
+
+EPUBJS.Events = function(obj, el){
+
+ this.events = {};
+
+ if(!el){
+ this.el = document.createElement('div');
+ }else{
+ this.el = el;
+ }
+
+ obj.createEvent = this.createEvent;
+ obj.tell = this.tell;
+ obj.listen = this.listen;
+ obj.deafen = this.deafen;
+ obj.listenUntil = this.listenUntil;
+
+ return this;
+};
+
+EPUBJS.Events.prototype.createEvent = function(evt){
+ var e = new CustomEvent(evt);
+ this.events[evt] = e;
+ return e;
+};
+
+EPUBJS.Events.prototype.tell = function(evt, msg){
+ var e;
+
+ if(!this.events[evt]){
+ console.warn("No event:", evt, "defined yet, creating.");
+ e = this.createEvent(evt);
+ }else{
+ e = this.events[evt];
+ }
+
+ if(msg) e.msg = msg;
+ this.el.dispatchEvent(e);
+
+};
+
+EPUBJS.Events.prototype.listen = function(evt, func, bindto){
+ if(!this.events[evt]){
+ console.warn("No event:", evt, "defined yet, creating.");
+ this.createEvent(evt);
+ return;
+ }
+
+ if(bindto){
+ this.el.addEventListener(evt, func.bind(bindto), false);
+ }else{
+ this.el.addEventListener(evt, func, false);
+ }
+
+};
+
+EPUBJS.Events.prototype.deafen = function(evt, func){
+ this.el.removeEventListener(evt, func, false);
+};
+
+EPUBJS.Events.prototype.listenUntil = function(OnEvt, OffEvt, func, bindto){
+ this.listen(OnEvt, func, bindto);
+
+ function unlisten(){
+ this.deafen(OnEvt, func);
+ this.deafen(OffEvt, unlisten);
+ }
+
+ this.listen(OffEvt, unlisten, this);
+};
+EPUBJS.hooks = {};
+EPUBJS.Hooks = (function(){
+ function hooks(){}
+
+ //-- Get pre-registered hooks
+ hooks.prototype.getHooks = function(){
+ var plugs;
+ this.hooks = {};
+ Array.prototype.slice.call(arguments).forEach(function(arg){
+ this.hooks[arg] = [];
+ }, this);
+
+ for (var plugType in this.hooks) {
+ plugs = EPUBJS.core.values(EPUBJS.hooks[plugType]);
+
+ plugs.forEach(function(hook){
+ this.registerHook(plugType, hook);
+ }, this);
+ }
+ };
+
+ //-- Hooks allow for injecting async functions that must all complete before continuing
+ // Functions must have a callback as their first argument.
+ hooks.prototype.registerHook = function(type, toAdd, toFront){
+
+ if(typeof(this.hooks[type]) != "undefined"){
+
+ if(typeof(toAdd) === "function"){
+ if(toFront) {
+ this.hooks[type].unshift(toAdd);
+ }else{
+ this.hooks[type].push(toAdd);
+ }
+ }else if(Array.isArray(toAdd)){
+ toAdd.forEach(function(hook){
+ if(toFront) {
+ this.hooks[type].unshift(hook);
+ }else{
+ this.hooks[type].push(hook);
+ }
+ }, this);
+ }
+ }else{
+ //-- Allows for undefined hooks
+ this.hooks[type] = [toAdd];
+
+ if(typeof(toAdd) === "function"){
+ this.hooks[type] = [toAdd];
+ }else if(Array.isArray(toAdd)){
+ this.hooks[type] = [];
+ toAdd.forEach(function(hook){
+ this.hooks[type].push(hook);
+ }, this);
+ }
+
+ }
+ };
+
+ hooks.prototype.removeHook = function(type, toRemove){
+ var index;
+
+ if(typeof(this.hooks[type]) != "undefined"){
+
+ if(typeof(toRemove) === "function"){
+ index = this.hooks[type].indexOf(toRemove);
+ if (index > -1) {
+ this.hooks[type].splice(index, 1);
+ }
+ }else if(Array.isArray(toRemove)){
+ toRemove.forEach(function(hook){
+ index = this.hooks[type].indexOf(hook);
+ if (index > -1) {
+ this.hooks[type].splice(index, 1);
+ }
+ }, this);
+ }
+ }
+ };
+
+ hooks.prototype.triggerHooks = function(type, callback, passed){
+ var hooks, count;
+
+ if(typeof(this.hooks[type]) == "undefined") return false;
+
+ hooks = this.hooks[type];
+
+ count = hooks.length;
+ if(count === 0 && callback) {
+ callback();
+ }
+
+ function countdown(){
+ count--;
+ if(count <= 0 && callback) callback();
+ }
+
+ hooks.forEach(function(hook){
+ hook(countdown, passed);
+ });
+ };
+
+ return {
+ register: function(name) {
+ if(EPUBJS.hooks[name] === undefined) { EPUBJS.hooks[name] = {}; }
+ if(typeof EPUBJS.hooks[name] !== 'object') { throw "Already registered: "+name; }
+ return EPUBJS.hooks[name];
+ },
+ mixin: function(object) {
+ for (var prop in hooks.prototype) {
+ object[prop] = hooks.prototype[prop];
+ }
+ }
+ };
+})();
+
+EPUBJS.Layout = EPUBJS.Layout || {};
+
+// EPUB2 documents won't provide us with "rendition:layout", so this is used to
+// duck type the documents instead.
+EPUBJS.Layout.isFixedLayout = function (documentElement) {
+ var viewport = documentElement.querySelector("[name=viewport]");
+ if (!viewport || !viewport.hasAttribute("content")) {
+ return false;
+ }
+ var content = viewport.getAttribute("content");
+ return /,/.test(content);
+};
+
+EPUBJS.Layout.Reflowable = function(){
+ this.documentElement = null;
+ this.spreadWidth = null;
+};
+
+EPUBJS.Layout.Reflowable.prototype.format = function(documentElement, _width, _height, _gap){
+ // Get the prefixed CSS commands
+ var columnAxis = EPUBJS.core.prefixed('columnAxis');
+ var columnGap = EPUBJS.core.prefixed('columnGap');
+ var columnWidth = EPUBJS.core.prefixed('columnWidth');
+ var columnFill = EPUBJS.core.prefixed('columnFill');
+
+ //-- Check the width and create even width columns
+ var width = Math.floor(_width);
+ // var width = (fullWidth % 2 === 0) ? fullWidth : fullWidth - 0; // Not needed for single
+ var section = Math.floor(width / 8);
+ var gap = (_gap >= 0) ? _gap : ((section % 2 === 0) ? section : section - 1);
+ this.documentElement = documentElement;
+ //-- Single Page
+ this.spreadWidth = (width + gap);
+
+
+ documentElement.style.overflow = "hidden";
+
+ // Must be set to the new calculated width or the columns will be off
+ documentElement.style.width = width + "px";
+
+ //-- Adjust height
+ documentElement.style.height = _height + "px";
+
+ //-- Add columns
+ documentElement.style[columnAxis] = "horizontal";
+ documentElement.style[columnFill] = "auto";
+ documentElement.style[columnWidth] = width+"px";
+ documentElement.style[columnGap] = gap+"px";
+ this.colWidth = width;
+ this.gap = gap;
+
+ return {
+ pageWidth : this.spreadWidth,
+ pageHeight : _height
+ };
+};
+
+EPUBJS.Layout.Reflowable.prototype.calculatePages = function() {
+ var totalWidth, displayedPages;
+ this.documentElement.style.width = "auto"; //-- reset width for calculations
+ totalWidth = this.documentElement.scrollWidth;
+ displayedPages = Math.ceil(totalWidth / this.spreadWidth);
+
+ return {
+ displayedPages : displayedPages,
+ pageCount : displayedPages
+ };
+};
+
+EPUBJS.Layout.ReflowableSpreads = function(){
+ this.documentElement = null;
+ this.spreadWidth = null;
+};
+
+EPUBJS.Layout.ReflowableSpreads.prototype.format = function(documentElement, _width, _height, _gap){
+ var columnAxis = EPUBJS.core.prefixed('columnAxis');
+ var columnGap = EPUBJS.core.prefixed('columnGap');
+ var columnWidth = EPUBJS.core.prefixed('columnWidth');
+ var columnFill = EPUBJS.core.prefixed('columnFill');
+
+ var divisor = 2,
+ cutoff = 800;
+
+ //-- Check the width and create even width columns
+ var fullWidth = Math.floor(_width);
+ var width = (fullWidth % 2 === 0) ? fullWidth : fullWidth - 1;
+
+ var section = Math.floor(width / 8);
+ var gap = (_gap >= 0) ? _gap : ((section % 2 === 0) ? section : section - 1);
+
+ //-- Double Page
+ var colWidth = Math.floor((width - gap) / divisor);
+
+ this.documentElement = documentElement;
+ this.spreadWidth = (colWidth + gap) * divisor;
+
+
+ documentElement.style.overflow = "hidden";
+
+ // Must be set to the new calculated width or the columns will be off
+ documentElement.style.width = width + "px";
+
+ //-- Adjust height
+ documentElement.style.height = _height + "px";
+
+ //-- Add columns
+ documentElement.style[columnAxis] = "horizontal";
+ documentElement.style[columnFill] = "auto";
+ documentElement.style[columnGap] = gap+"px";
+ documentElement.style[columnWidth] = colWidth+"px";
+
+ this.colWidth = colWidth;
+ this.gap = gap;
+ return {
+ pageWidth : this.spreadWidth,
+ pageHeight : _height
+ };
+};
+
+EPUBJS.Layout.ReflowableSpreads.prototype.calculatePages = function() {
+ var totalWidth = this.documentElement.scrollWidth;
+ var displayedPages = Math.ceil(totalWidth / this.spreadWidth);
+
+ //-- Add a page to the width of the document to account an for odd number of pages
+ this.documentElement.style.width = ((displayedPages * this.spreadWidth) - this.gap) + "px";
+
+ return {
+ displayedPages : displayedPages,
+ pageCount : displayedPages * 2
+ };
+};
+
+EPUBJS.Layout.Fixed = function(){
+ this.documentElement = null;
+};
+
+EPUBJS.Layout.Fixed.prototype.format = function(documentElement, _width, _height, _gap){
+ var columnWidth = EPUBJS.core.prefixed('columnWidth');
+ var transform = EPUBJS.core.prefixed('transform');
+ var transformOrigin = EPUBJS.core.prefixed('transformOrigin');
+ var viewport = documentElement.querySelector("[name=viewport]");
+ var content;
+ var contents;
+ var width, height;
+ this.documentElement = documentElement;
+ /**
+ * check for the viewport size
+ * <meta name="viewport" content="width=1024,height=697" />
+ */
+ if(viewport && viewport.hasAttribute("content")) {
+ content = viewport.getAttribute("content");
+ contents = content.split(',');
+ if(contents[0]){
+ width = contents[0].replace("width=", '');
+ }
+ if(contents[1]){
+ height = contents[1].replace("height=", '');
+ }
+ }
+
+ //-- Scale fixed documents so their contents don't overflow, and
+ // vertically and horizontally center the contents
+ var widthScale = _width / width;
+ var heightScale = _height / height;
+ var scale = widthScale < heightScale ? widthScale : heightScale;
+ documentElement.style.position = "absolute";
+ documentElement.style.top = "50%";
+ documentElement.style.left = "50%";
+ documentElement.style[transform] = "scale(" + scale + ") translate(-50%, -50%)";
+ documentElement.style[transformOrigin] = "0px 0px 0px";
+
+ //-- Adjust width and height
+ documentElement.style.width = width + "px" || "auto";
+ documentElement.style.height = height + "px" || "auto";
+
+ //-- Remove columns
+ documentElement.style[columnWidth] = "auto";
+
+ //-- Scroll
+ documentElement.style.overflow = "auto";
+
+ this.colWidth = width;
+ this.gap = 0;
+
+ return {
+ pageWidth : width,
+ pageHeight : height
+ };
+
+};
+
+EPUBJS.Layout.Fixed.prototype.calculatePages = function(){
+ return {
+ displayedPages : 1,
+ pageCount : 1
+ };
+};
+
+EPUBJS.Locations = function(spine, store, credentials) {
+ this.spine = spine;
+ this.store = store;
+ this.credentials = credentials;
+
+ this.epubcfi = new EPUBJS.EpubCFI();
+
+ this._locations = [];
+ this.total = 0;
+
+ this.break = 150;
+
+ this._current = 0;
+
+};
+
+EPUBJS.Locations.prototype.generate = function(chars) {
+ var deferred = new RSVP.defer();
+ var spinePos = -1;
+ var spineLength = this.spine.length;
+ var finished;
+ var nextChapter = function(deferred){
+ var chapter;
+ var next = spinePos + 1;
+ var done = deferred || new RSVP.defer();
+ var loaded;
+ if(next >= spineLength) {
+ done.resolve();
+ } else {
+ spinePos = next;
+ chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store, this.credentials);
+
+ this.process(chapter).then(function() {
+ // Load up the next chapter
+ setTimeout(function(){
+ nextChapter(done);
+ }, 1);
+
+ });
+ }
+ return done.promise;
+ }.bind(this);
+
+ if(typeof chars === 'number') {
+ this.break = chars;
+ }
+
+ finished = nextChapter().then(function(){
+ this.total = this._locations.length-1;
+
+ if (this._currentCfi) {
+ this.currentLocation = this._currentCfi;
+ }
+ deferred.resolve(this._locations);
+ }.bind(this));
+
+ return deferred.promise;
+};
+
+EPUBJS.Locations.prototype.process = function(chapter) {
+ return chapter.load()
+ .then(function(_doc) {
+
+ var range;
+ var doc = _doc;
+ var contents = doc.documentElement.querySelector("body");
+ var counter = 0;
+ var prev;
+ var cfi;
+
+ this.sprint(contents, function(node) {
+ var len = node.length;
+ var dist;
+ var pos = 0;
+
+ // Start range
+ if (counter === 0) {
+ range = doc.createRange();
+ range.setStart(node, 0);
+ }
+
+ dist = this.break - counter;
+
+ // Node is smaller than a break
+ if(dist > len){
+ counter += len;
+ pos = len;
+ }
+
+ while (pos < len) {
+ counter = this.break;
+ pos += this.break;
+
+ // Gone over
+ if(pos >= len){
+ // Continue counter for next node
+ counter = len - (pos - this.break);
+
+ // At End
+ } else {
+ // End the previous range
+ range.setEnd(node, pos);
+ cfi = chapter.cfiFromRange(range);
+ this._locations.push(cfi);
+ counter = 0;
+
+ // Start new range
+ pos += 1;
+ range = doc.createRange();
+ range.setStart(node, pos);
+ }
+
+ }
+
+ prev = node;
+
+ }.bind(this));
+
+ // Close remaining
+ if (range) {
+ range.setEnd(prev, prev.length);
+ cfi = chapter.cfiFromRange(range);
+ this._locations.push(cfi);
+ counter = 0;
+ }
+
+ }.bind(this));
+
+};
+
+EPUBJS.Locations.prototype.sprint = function(root, func) {
+ var node;
+ var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
+
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+
+};
+
+EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
+ // Check if the location has not been set yet
+ if(this._locations.length === 0) {
+ return -1;
+ }
+
+ return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
+};
+
+EPUBJS.Locations.prototype.percentageFromCfi = function(cfi) {
+ // Find closest cfi
+ var loc = this.locationFromCfi(cfi);
+ // Get percentage in total
+ return this.percentageFromLocation(loc);
+};
+
+EPUBJS.Locations.prototype.percentageFromLocation = function(loc) {
+ if (!loc || !this.total) {
+ return 0;
+ }
+ return (loc / this.total);
+};
+
+EPUBJS.Locations.prototype.cfiFromLocation = function(loc){
+ var cfi = -1;
+ // check that pg is an int
+ if(typeof loc != "number"){
+ loc = parseInt(loc);
+ }
+
+ if(loc >= 0 && loc < this._locations.length) {
+ cfi = this._locations[loc];
+ }
+
+ return cfi;
+};
+
+EPUBJS.Locations.prototype.cfiFromPercentage = function(value){
+ var percentage = (value > 1) ? value / 100 : value; // Normalize value to 0-1
+ var loc = Math.ceil(this.total * percentage);
+
+ return this.cfiFromLocation(loc);
+};
+
+EPUBJS.Locations.prototype.load = function(locations){
+ this._locations = JSON.parse(locations);
+ this.total = this._locations.length-1;
+ return this._locations;
+};
+
+EPUBJS.Locations.prototype.save = function(json){
+ return JSON.stringify(this._locations);
+};
+
+EPUBJS.Locations.prototype.getCurrent = function(json){
+ return this._current;
+};
+
+EPUBJS.Locations.prototype.setCurrent = function(curr){
+ var loc;
+
+ if(typeof curr == "string"){
+ this._currentCfi = curr;
+ } else if (typeof curr == "number") {
+ this._current = curr;
+ } else {
+ return;
+ }
+
+ if(this._locations.length === 0) {
+ return;
+ }
+
+ if(typeof curr == "string"){
+ loc = this.locationFromCfi(curr);
+ this._current = loc;
+ } else {
+ loc = curr;
+ }
+
+ this.trigger("changed", {
+ percentage: this.percentageFromLocation(loc)
+ });
+};
+
+Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
+ get: function () {
+ return this._current;
+ },
+ set: function (curr) {
+ this.setCurrent(curr);
+ }
+});
+
+RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);
+
+EPUBJS.Pagination = function(pageList) {
+ this.pages = [];
+ this.locations = [];
+ this.epubcfi = new EPUBJS.EpubCFI();
+ if(pageList && pageList.length) {
+ this.process(pageList);
+ }
+};
+
+EPUBJS.Pagination.prototype.process = function(pageList){
+ pageList.forEach(function(item){
+ this.pages.push(item.page);
+ this.locations.push(item.cfi);
+ }, this);
+
+ this.pageList = pageList;
+ this.firstPage = parseInt(this.pages[0]);
+ this.lastPage = parseInt(this.pages[this.pages.length-1]);
+ this.totalPages = this.lastPage - this.firstPage;
+};
+
+EPUBJS.Pagination.prototype.pageFromCfi = function(cfi){
+ var pg = -1;
+
+ // Check if the pageList has not been set yet
+ if(this.locations.length === 0) {
+ return -1;
+ }
+
+ // TODO: check if CFI is valid?
+
+ // check if the cfi is in the location list
+ // var index = this.locations.indexOf(cfi);
+ var index = EPUBJS.core.indexOfSorted(cfi, this.locations, this.epubcfi.compare);
+ if(index != -1) {
+ pg = this.pages[index];
+ } else {
+ // Otherwise add it to the list of locations
+ // Insert it in the correct position in the locations page
+ //index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
+ index = EPUBJS.core.locationOf(cfi, this.locations, this.epubcfi.compare);
+ // Get the page at the location just before the new one, or return the first
+ pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
+ if(pg !== undefined) {
+ // Add the new page in so that the locations and page array match up
+ //this.pages.splice(index, 0, pg);
+ } else {
+ pg = -1;
+ }
+
+ }
+ return pg;
+};
+
+EPUBJS.Pagination.prototype.cfiFromPage = function(pg){
+ var cfi = -1;
+ // check that pg is an int
+ if(typeof pg != "number"){
+ pg = parseInt(pg);
+ }
+
+ // check if the cfi is in the page list
+ // Pages could be unsorted.
+ var index = this.pages.indexOf(pg);
+ if(index != -1) {
+ cfi = this.locations[index];
+ }
+ // TODO: handle pages not in the list
+ return cfi;
+};
+
+EPUBJS.Pagination.prototype.pageFromPercentage = function(percent){
+ var pg = Math.round(this.totalPages * percent);
+ return pg;
+};
+
+// Returns a value between 0 - 1 corresponding to the location of a page
+EPUBJS.Pagination.prototype.percentageFromPage = function(pg){
+ var percentage = (pg - this.firstPage) / this.totalPages;
+ return Math.round(percentage * 1000) / 1000;
+};
+
+// Returns a value between 0 - 1 corresponding to the location of a cfi
+EPUBJS.Pagination.prototype.percentageFromCfi = function(cfi){
+ var pg = this.pageFromCfi(cfi);
+ var percentage = this.percentageFromPage(pg);
+ return percentage;
+};
+EPUBJS.Parser = function(baseUrl){
+ this.baseUrl = baseUrl || '';
+};
+
+EPUBJS.Parser.prototype.container = function(containerXml){
+ //-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
+ var rootfile, fullpath, folder, encoding;
+
+ if(!containerXml) {
+ console.error("Container File Not Found");
+ return;
+ }
+
+ rootfile = containerXml.querySelector("rootfile");
+
+ if(!rootfile) {
+ console.error("No RootFile Found");
+ return;
+ }
+
+ fullpath = rootfile.getAttribute('full-path');
+ folder = EPUBJS.core.uri(fullpath).directory;
+ encoding = containerXml.xmlEncoding;
+
+ //-- Now that we have the path we can parse the contents
+ return {
+ 'packagePath' : fullpath,
+ 'basePath' : folder,
+ 'encoding' : encoding
+ };
+};
+
+EPUBJS.Parser.prototype.identifier = function(packageXml){
+ var metadataNode;
+
+ if(!packageXml) {
+ console.error("Package File Not Found");
+ return;
+ }
+
+ metadataNode = packageXml.querySelector("metadata");
+
+ if(!metadataNode) {
+ console.error("No Metadata Found");
+ return;
+ }
+
+ return this.getElementText(metadataNode, "identifier");
+};
+
+EPUBJS.Parser.prototype.packageContents = function(packageXml, baseUrl){
+ var parse = this;
+ var metadataNode, manifestNode, spineNode;
+ var manifest, navPath, tocPath, coverPath;
+ var spineNodeIndex;
+ var spine;
+ var spineIndexByURL;
+ var metadata;
+
+ if(baseUrl) this.baseUrl = baseUrl;
+
+ if(!packageXml) {
+ console.error("Package File Not Found");
+ return;
+ }
+
+ metadataNode = packageXml.querySelector("metadata");
+ if(!metadataNode) {
+ console.error("No Metadata Found");
+ return;
+ }
+
+ manifestNode = packageXml.querySelector("manifest");
+ if(!manifestNode) {
+ console.error("No Manifest Found");
+ return;
+ }
+
+ spineNode = packageXml.querySelector("spine");
+ if(!spineNode) {
+ console.error("No Spine Found");
+ return;
+ }
+
+ manifest = parse.manifest(manifestNode);
+ navPath = parse.findNavPath(manifestNode);
+ tocPath = parse.findTocPath(manifestNode, spineNode);
+ coverPath = parse.findCoverPath(packageXml);
+
+ spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
+
+ spine = parse.spine(spineNode, manifest);
+
+ spineIndexByURL = {};
+ spine.forEach(function(item){
+ spineIndexByURL[item.href] = item.index;
+ });
+
+ metadata = parse.metadata(metadataNode);
+
+ metadata.direction = spineNode.getAttribute("page-progression-direction");
+
+ return {
+ 'metadata' : metadata,
+ 'spine' : spine,
+ 'manifest' : manifest,
+ 'navPath' : navPath,
+ 'tocPath' : tocPath,
+ 'coverPath': coverPath,
+ 'spineNodeIndex' : spineNodeIndex,
+ 'spineIndexByURL' : spineIndexByURL
+ };
+};
+
+//-- Find TOC NAV
+EPUBJS.Parser.prototype.findNavPath = function(manifestNode){
+ // Find item with property 'nav'
+ // Should catch nav irregardless of order
+ var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
+ return node ? node.getAttribute('href') : false;
+};
+
+//-- Find TOC NCX: media-type="application/x-dtbncx+xml" href="toc.ncx"
+EPUBJS.Parser.prototype.findTocPath = function(manifestNode, spineNode){
+ var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
+ var tocId;
+
+ // If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
+ // according to http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4.1.2,
+ // "The item that describes the NCX must be referenced by the spine toc attribute."
+ if (!node) {
+ tocId = spineNode.getAttribute("toc");
+ if(tocId) {
+ node = manifestNode.querySelector("item[id='" + tocId + "']");
+ }
+ }
+
+ return node ? node.getAttribute('href') : false;
+};
+
+//-- Expanded to match Readium web components
+EPUBJS.Parser.prototype.metadata = function(xml){
+ var metadata = {},
+ p = this;
+
+ metadata.bookTitle = p.getElementText(xml, 'title');
+ metadata.creator = p.getElementText(xml, 'creator');
+ metadata.description = p.getElementText(xml, 'description');
+
+ metadata.pubdate = p.getElementText(xml, 'date');
+
+ metadata.publisher = p.getElementText(xml, 'publisher');
+
+ metadata.identifier = p.getElementText(xml, "identifier");
+ metadata.language = p.getElementText(xml, "language");
+ metadata.rights = p.getElementText(xml, "rights");
+
+ metadata.modified_date = p.querySelectorText(xml, "meta[property='dcterms:modified']");
+ metadata.layout = p.querySelectorText(xml, "meta[property='rendition:layout']");
+ metadata.orientation = p.querySelectorText(xml, "meta[property='rendition:orientation']");
+ metadata.spread = p.querySelectorText(xml, "meta[property='rendition:spread']");
+
+ return metadata;
+};
+
+//-- Find Cover: <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
+//-- Fallback for Epub 2.0
+EPUBJS.Parser.prototype.findCoverPath = function(packageXml){
+
+ var epubVersion = packageXml.querySelector('package').getAttribute('version');
+ if (epubVersion === '2.0') {
+ var metaCover = packageXml.querySelector('meta[name="cover"]');
+ if (metaCover) {
+ var coverId = metaCover.getAttribute('content');
+ var cover = packageXml.querySelector("item[id='" + coverId + "']");
+ return cover ? cover.getAttribute('href') : false;
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ var node = packageXml.querySelector("item[properties='cover-image']");
+ return node ? node.getAttribute('href') : false;
+ }
+};
+
+EPUBJS.Parser.prototype.getElementText = function(xml, tag){
+ var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
+ el;
+
+ if(!found || found.length === 0) return '';
+
+ el = found[0];
+
+ if(el.childNodes.length){
+ return el.childNodes[0].nodeValue;
+ }
+
+ return '';
+
+};
+
+EPUBJS.Parser.prototype.querySelectorText = function(xml, q){
+ var el = xml.querySelector(q);
+
+ if(el && el.childNodes.length){
+ return el.childNodes[0].nodeValue;
+ }
+
+ return '';
+};
+
+EPUBJS.Parser.prototype.manifest = function(manifestXml){
+ var baseUrl = this.baseUrl,
+ manifest = {};
+
+ //-- Turn items into an array
+ var selected = manifestXml.querySelectorAll("item"),
+ items = Array.prototype.slice.call(selected);
+
+ //-- Create an object with the id as key
+ items.forEach(function(item){
+ var id = item.getAttribute('id'),
+ href = item.getAttribute('href') || '',
+ type = item.getAttribute('media-type') || '',
+ properties = item.getAttribute('properties') || '';
+
+ manifest[id] = {
+ 'href' : href,
+ 'url' : baseUrl + href, //-- Absolute URL for loading with a web worker
+ 'type' : type,
+ 'properties' : properties
+ };
+
+ });
+
+ return manifest;
+
+};
+
+EPUBJS.Parser.prototype.spine = function(spineXml, manifest){
+ var spine = [];
+
+ var selected = spineXml.getElementsByTagName("itemref"),
+ items = Array.prototype.slice.call(selected);
+
+ var spineNodeIndex = Array.prototype.indexOf.call(spineXml.parentNode.childNodes, spineXml);
+
+ var epubcfi = new EPUBJS.EpubCFI();
+
+ //-- Add to array to mantain ordering and cross reference with manifest
+ items.forEach(function(item, index){
+ var Id = item.getAttribute('idref');
+ var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
+ var props = item.getAttribute('properties') || '';
+ var propArray = props.length ? props.split(' ') : [];
+ var manifestProps = manifest[Id].properties;
+ var manifestPropArray = manifestProps.length ? manifestProps.split(' ') : [];
+ var vert = {
+ 'id' : Id,
+ 'linear' : item.getAttribute('linear') || '',
+ 'properties' : propArray,
+ 'manifestProperties' : manifestPropArray,
+ 'href' : manifest[Id].href,
+ 'url' : manifest[Id].url,
+ 'index' : index,
+ 'cfiBase' : cfiBase,
+ 'cfi' : "epubcfi(" + cfiBase + ")"
+ };
+ spine.push(vert);
+ });
+
+ return spine;
+};
+
+EPUBJS.Parser.prototype.querySelectorByType = function(html, element, type){
+ var query = html.querySelector(element+'[*|type="'+type+'"]');
+ // Handle IE not supporting namespaced epub:type in querySelector
+ if(query === null || query.length === 0) {
+ query = html.querySelectorAll(element);
+ for (var i = 0; i < query.length; i++) {
+ if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type) {
+ return query[i];
+ }
+ }
+ } else {
+ return query;
+ }
+};
+
+EPUBJS.Parser.prototype.nav = function (navHtml, spineIndexByURL, bookSpine) {
+ var toc = this.querySelectorByType(navHtml, 'nav', 'toc');
+ return this.navItems(toc, spineIndexByURL, bookSpine);
+};
+
+EPUBJS.Parser.prototype.navItems = function (navNode, spineIndexByURL, bookSpine) {
+ if (!navNode) return [];
+
+ var list = navNode.querySelector('ol');
+ if (!list) return [];
+
+ var items = list.childNodes,
+ result = [];
+
+ Array.prototype.forEach.call(items, function (item) {
+ if (item.tagName !== 'li') return;
+
+ var content = item.querySelector('a, span'),
+ href = content.getAttribute('href') || '',
+ label = content.textContent || '',
+ split = href.split('#'),
+ baseUrl = split[0],
+ spinePos = spineIndexByURL[baseUrl],
+ spineItem = bookSpine[spinePos],
+ cfi = spineItem ? spineItem.cfi : '',
+ subitems = this.navItems(item, spineIndexByURL, bookSpine);
+
+ result.push({
+ href: href,
+ label: label,
+ spinePos: spinePos,
+ subitems: subitems,
+ cfi: cfi
+ });
+ }.bind(this));
+
+ return result;
+};
+
+EPUBJS.Parser.prototype.toc = function(tocXml, spineIndexByURL, bookSpine){
+ var navPoints = tocXml.querySelectorAll("navMap navPoint");
+ var length = navPoints.length;
+ var i;
+ var toc = {};
+ var list = [];
+ var item, parent;
+
+ if(!navPoints || length === 0) return list;
+
+ for (i = 0; i < length; ++i) {
+ item = this.tocItem(navPoints[i], spineIndexByURL, bookSpine);
+ toc[item.id] = item;
+ if(!item.parent) {
+ list.push(item);
+ } else {
+ parent = toc[item.parent];
+ parent.subitems.push(item);
+ }
+ }
+
+ return list;
+};
+
+EPUBJS.Parser.prototype.tocItem = function(item, spineIndexByURL, bookSpine){
+ var id = item.getAttribute('id') || false,
+ content = item.querySelector("content"),
+ src = content.getAttribute('src'),
+ navLabel = item.querySelector("navLabel"),
+ text = navLabel.textContent ? navLabel.textContent : "",
+ split = src.split("#"),
+ baseUrl = split[0],
+ spinePos = spineIndexByURL[baseUrl],
+ spineItem = bookSpine[spinePos],
+ subitems = [],
+ parentNode = item.parentNode,
+ parent,
+ cfi = spineItem ? spineItem.cfi : '';
+
+ if(parentNode && parentNode.nodeName === "navPoint") {
+ parent = parentNode.getAttribute('id');
+ }
+
+ if(!id) {
+ if(spinePos) {
+ spineItem = bookSpine[spinePos];
+ id = spineItem.id;
+ cfi = spineItem.cfi;
+ } else {
+ id = 'epubjs-autogen-toc-id-' + EPUBJS.core.uuid();
+ item.setAttribute('id', id);
+ }
+ }
+
+ return {
+ "id": id,
+ "href": src,
+ "label": text,
+ "spinePos": spinePos,
+ "subitems" : subitems,
+ "parent" : parent,
+ "cfi" : cfi
+ };
+};
+
+
+EPUBJS.Parser.prototype.pageList = function(navHtml, spineIndexByURL, bookSpine){
+ var navElement = this.querySelectorByType(navHtml, "nav", "page-list");
+ var navItems = navElement ? navElement.querySelectorAll("ol li") : [];
+ var length = navItems.length;
+ var i;
+ var toc = {};
+ var list = [];
+ var item;
+
+ if(!navItems || length === 0) return list;
+
+ for (i = 0; i < length; ++i) {
+ item = this.pageListItem(navItems[i], spineIndexByURL, bookSpine);
+ list.push(item);
+ }
+
+ return list;
+};
+
+EPUBJS.Parser.prototype.pageListItem = function(item, spineIndexByURL, bookSpine){
+ var id = item.getAttribute('id') || false,
+ content = item.querySelector("a"),
+ href = content.getAttribute('href') || '',
+ text = content.textContent || "",
+ page = parseInt(text),
+ isCfi = href.indexOf("epubcfi"),
+ split,
+ packageUrl,
+ cfi;
+
+ if(isCfi != -1) {
+ split = href.split("#");
+ packageUrl = split[0];
+ cfi = split.length > 1 ? split[1] : false;
+ return {
+ "cfi" : cfi,
+ "href" : href,
+ "packageUrl" : packageUrl,
+ "page" : page
+ };
+ } else {
+ return {
+ "href" : href,
+ "page" : page
+ };
+ }
+};
+
+EPUBJS.Render.Iframe = function() {
+ this.iframe = null;
+ this.document = null;
+ this.window = null;
+ this.docEl = null;
+ this.bodyEl = null;
+
+ this.leftPos = 0;
+ this.pageWidth = 0;
+ this.id = EPUBJS.core.uuid();
+};
+
+//-- Build up any html needed
+EPUBJS.Render.Iframe.prototype.create = function(){
+ this.element = document.createElement('div');
+ this.element.id = "epubjs-view:" + this.id
+
+ this.isMobile = navigator.userAgent.match(/(iPad|iPhone|iPod|Mobile|Android)/g);
+ this.transform = EPUBJS.core.prefixed('transform');
+
+ return this.element;
+};
+
+EPUBJS.Render.Iframe.prototype.addIframe = function(){
+ this.iframe = document.createElement('iframe');
+ this.iframe.id = "epubjs-iframe:" + this.id;
+ this.iframe.scrolling = this.scrolling || "no";
+ this.iframe.seamless = "seamless";
+ // Back up if seamless isn't supported
+ this.iframe.style.border = "none";
+
+ this.iframe.addEventListener("load", this.loaded.bind(this), false);
+
+ if (this._width || this._height) {
+ this.iframe.height = this._height;
+ this.iframe.width = this._width;
+ }
+ return this.iframe;
+};
+
+/**
+* Sets the source of the iframe with the given URL string
+* Takes: Document Contents String
+* Returns: promise with document element
+*/
+EPUBJS.Render.Iframe.prototype.load = function(contents, url){
+ var render = this,
+ deferred = new RSVP.defer();
+
+ if(this.window) {
+ this.unload();
+ }
+
+ if (this.iframe) {
+ this.element.removeChild(this.iframe);
+ }
+
+ this.iframe = this.addIframe();
+ this.element.appendChild(this.iframe);
+
+
+ this.iframe.onload = function(e) {
+ var title;
+
+ render.document = render.iframe.contentDocument;
+ render.docEl = render.document.documentElement;
+ render.headEl = render.document.head;
+ render.bodyEl = render.document.body || render.document.querySelector("body");
+ render.window = render.iframe.contentWindow;
+
+ render.window.addEventListener("resize", render.resized.bind(render), false);
+
+ // Reset the scroll position
+ render.leftPos = 0;
+ render.setLeft(0);
+
+ //-- Clear Margins
+ if(render.bodyEl) {
+ render.bodyEl.style.margin = "0";
+ }
+
+ deferred.resolve(render.docEl);
+ };
+
+ this.iframe.onerror = function(e) {
+ //console.error("Error Loading Contents", e);
+ deferred.reject({
+ message : "Error Loading Contents: " + e,
+ stack : new Error().stack
+ });
+ };
+
+ // this.iframe.contentWindow.location.replace(url);
+ this.document = this.iframe.contentDocument;
+
+ if(!this.document) {
+ deferred.reject(new Error("No Document Available"));
+ return deferred.promise;
+ }
+
+ this.iframe.contentDocument.open();
+ this.iframe.contentDocument.write(contents);
+ this.iframe.contentDocument.close();
+
+ return deferred.promise;
+};
+
+
+EPUBJS.Render.Iframe.prototype.loaded = function(v){
+ var url = this.iframe.contentWindow.location.href;
+ var baseEl, base;
+
+ this.document = this.iframe.contentDocument;
+ this.docEl = this.document.documentElement;
+ this.headEl = this.document.head;
+ this.bodyEl = this.document.body || this.document.querySelector("body");
+ this.window = this.iframe.contentWindow;
+ this.window.focus();
+
+ if(url != "about:blank"){
+ baseEl = this.iframe.contentDocument.querySelector("base");
+ base = baseEl.getAttribute('href');
+ this.trigger("render:loaded", base);
+ }
+
+};
+
+// Resize the iframe to the given width and height
+EPUBJS.Render.Iframe.prototype.resize = function(width, height){
+ var iframeBox;
+
+ if(!this.element) return;
+
+ this.element.style.height = height;
+
+
+ if(!isNaN(width) && width % 2 !== 0){
+ width += 1; //-- Prevent cutting off edges of text in columns
+ }
+
+ this.element.style.width = width;
+
+ if (this.iframe) {
+ this.iframe.height = height;
+ this.iframe.width = width;
+ }
+
+ // Set the width for the iframe
+ this._height = height;
+ this._width = width;
+
+ // Get the fractional height and width of the iframe
+ // Default to orginal if bounding rect is 0
+ this.width = this.element.getBoundingClientRect().width || width;
+ this.height = this.element.getBoundingClientRect().height || height;
+
+};
+
+
+EPUBJS.Render.Iframe.prototype.resized = function(e){
+ // Get the fractional height and width of the iframe
+ this.width = this.iframe.getBoundingClientRect().width;
+ this.height = this.iframe.getBoundingClientRect().height;
+};
+
+EPUBJS.Render.Iframe.prototype.totalWidth = function(){
+ return this.docEl.scrollWidth;
+};
+
+EPUBJS.Render.Iframe.prototype.totalHeight = function(){
+ return this.docEl.scrollHeight;
+};
+
+EPUBJS.Render.Iframe.prototype.setPageDimensions = function(pageWidth, pageHeight){
+ this.pageWidth = pageWidth;
+ this.pageHeight = pageHeight;
+ //-- Add a page to the width of the document to account an for odd number of pages
+ // this.docEl.style.width = this.docEl.scrollWidth + pageWidth + "px";
+};
+
+EPUBJS.Render.Iframe.prototype.setDirection = function(direction){
+
+ this.direction = direction;
+
+ // Undo previous changes if needed
+ if(this.docEl && this.docEl.dir == "rtl"){
+ this.docEl.dir = "rtl";
+ if (this.layout !== "pre-paginated") {
+ this.docEl.style.position = "static";
+ this.docEl.style.right = "auto";
+ }
+ }
+
+};
+
+EPUBJS.Render.Iframe.prototype.setLeft = function(leftPos){
+ // this.bodyEl.style.marginLeft = -leftPos + "px";
+ // this.docEl.style.marginLeft = -leftPos + "px";
+ // this.docEl.style[EPUBJS.Render.Iframe.transform] = 'translate('+ (-leftPos) + 'px, 0)';
+
+ if (this.isMobile) {
+ this.docEl.style[this.transform] = 'translate('+ (-leftPos) + 'px, 0)';
+ } else {
+ this.document.defaultView.scrollTo(leftPos, 0);
+ }
+
+};
+
+EPUBJS.Render.Iframe.prototype.setLayout = function (layout) {
+ this.layout = layout;
+};
+
+EPUBJS.Render.Iframe.prototype.setStyle = function(style, val, prefixed){
+ if(prefixed) {
+ style = EPUBJS.core.prefixed(style);
+ }
+
+ if(this.bodyEl) this.bodyEl.style[style] = val;
+};
+
+EPUBJS.Render.Iframe.prototype.removeStyle = function(style){
+
+ if(this.bodyEl) this.bodyEl.style[style] = '';
+
+};
+
+EPUBJS.Render.Iframe.prototype.addHeadTag = function(tag, attrs, _doc) {
+ var doc = _doc || this.document;
+ var tagEl = doc.createElement(tag);
+ var headEl = doc.head;
+
+ for(var attr in attrs) {
+ tagEl.setAttribute(attr, attrs[attr]);
+ }
+
+ if(headEl) headEl.insertBefore(tagEl, headEl.firstChild);
+};
+
+EPUBJS.Render.Iframe.prototype.page = function(pg){
+ this.leftPos = this.pageWidth * (pg-1); //-- pages start at 1
+
+ // Reverse for rtl langs
+ if(this.direction === "rtl"){
+ this.leftPos = this.leftPos * -1;
+ }
+
+ this.setLeft(this.leftPos);
+};
+
+//-- Show the page containing an Element
+EPUBJS.Render.Iframe.prototype.getPageNumberByElement = function(el){
+ var left, pg;
+ if(!el) return;
+
+ left = this.leftPos + el.getBoundingClientRect().left; //-- Calculate left offset compaired to scrolled position
+
+ pg = Math.floor(left / this.pageWidth) + 1; //-- pages start at 1
+
+ return pg;
+};
+
+//-- Show the page containing an Element
+EPUBJS.Render.Iframe.prototype.getPageNumberByRect = function(boundingClientRect){
+ var left, pg;
+
+ left = this.leftPos + boundingClientRect.left; //-- Calculate left offset compaired to scrolled position
+ pg = Math.floor(left / this.pageWidth) + 1; //-- pages start at 1
+
+ return pg;
+};
+
+// Return the root element of the content
+EPUBJS.Render.Iframe.prototype.getBaseElement = function(){
+ return this.bodyEl;
+};
+
+// Return the document element
+EPUBJS.Render.Iframe.prototype.getDocumentElement = function(){
+ return this.docEl;
+};
+
+// Checks if an element is on the screen
+EPUBJS.Render.Iframe.prototype.isElementVisible = function(el){
+ var rect;
+ var left;
+
+ if(el && typeof el.getBoundingClientRect === 'function'){
+ rect = el.getBoundingClientRect();
+ left = rect.left; //+ rect.width;
+ if( rect.width !== 0 &&
+ rect.height !== 0 && // Element not visible
+ left >= 0 &&
+ left < this.pageWidth ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+
+EPUBJS.Render.Iframe.prototype.scroll = function(bool){
+ if(bool) {
+ // this.iframe.scrolling = "yes";
+ this.scrolling = "yes";
+ } else {
+ this.scrolling = "no";
+ // this.iframe.scrolling = "no";
+ }
+};
+
+// Cleanup event listeners
+EPUBJS.Render.Iframe.prototype.unload = function(){
+ this.window.removeEventListener("resize", this.resized);
+ this.iframe.removeEventListener("load", this.loaded);
+};
+
+//-- Enable binding events to Render
+RSVP.EventTarget.mixin(EPUBJS.Render.Iframe.prototype);
+
+EPUBJS.Renderer = function(renderMethod, hidden) {
+ // Dom events to listen for
+ this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click"];
+ this.upEvent = "mouseup";
+ this.downEvent = "mousedown";
+ if('ontouchstart' in document.documentElement) {
+ this.listenedEvents.push("touchstart", "touchend");
+ this.upEvent = "touchend";
+ this.downEvent = "touchstart";
+ }
+ /**
+ * Setup a render method.
+ * Options are: Iframe
+ */
+ if(renderMethod && typeof(EPUBJS.Render[renderMethod]) != "undefined"){
+ this.render = new EPUBJS.Render[renderMethod]();
+ } else {
+ console.error("Not a Valid Rendering Method");
+ }
+
+ // Listen for load events
+ this.render.on("render:loaded", this.loaded.bind(this));
+
+ // Cached for replacement urls from storage
+ this.caches = {};
+
+ // Blank Cfi for Parsing
+ this.epubcfi = new EPUBJS.EpubCFI();
+
+ this.spreads = true;
+ this.isForcedSingle = false;
+ this.resized = this.onResized.bind(this);
+
+ this.layoutSettings = {};
+
+ this.hidden = hidden || false;
+ //-- Adds Hook methods to the Book prototype
+ // Hooks will all return before triggering the callback.
+ EPUBJS.Hooks.mixin(this);
+ //-- Get pre-registered hooks for events
+ this.getHooks("beforeChapterDisplay");
+
+ //-- Queue up page changes if page map isn't ready
+ this._q = EPUBJS.core.queue(this);
+
+ this._moving = false;
+
+};
+
+//-- Renderer events for listening
+EPUBJS.Renderer.prototype.Events = [
+ "renderer:keydown",
+ "renderer:keyup",
+ "renderer:keypressed",
+ "renderer:mouseup",
+ "renderer:mousedown",
+ "renderer:click",
+ "renderer:touchstart",
+ "renderer:touchend",
+ "renderer:selected",
+ "renderer:chapterUnload",
+ "renderer:chapterUnloaded",
+ "renderer:chapterDisplayed",
+ "renderer:locationChanged",
+ "renderer:visibleLocationChanged",
+ "renderer:visibleRangeChanged",
+ "renderer:resized",
+ "renderer:spreads",
+ "renderer:beforeResize"
+];
+
+/**
+* Creates an element to render to.
+* Resizes to passed width and height or to the elements size
+*/
+EPUBJS.Renderer.prototype.initialize = function(element, width, height){
+ this.container = element;
+ this.element = this.render.create();
+
+ this.initWidth = width;
+ this.initHeight = height;
+
+ this.width = width || this.container.clientWidth;
+ this.height = height || this.container.clientHeight;
+
+ this.container.appendChild(this.element);
+
+ if(width && height){
+ this.render.resize(this.width, this.height);
+ } else {
+ this.render.resize('100%', '100%');
+ }
+
+ document.addEventListener("orientationchange", this.onResized.bind(this));
+};
+
+/**
+* Display a chapter
+* Takes: chapter object, global layout settings
+* Returns: Promise with passed Renderer after pages has loaded
+*/
+EPUBJS.Renderer.prototype.displayChapter = function(chapter, globalLayout){
+ var store = false;
+ if(this._moving) {
+ console.warning("Rendering In Progress");
+ var deferred = new RSVP.defer();
+ deferred.reject({
+ message : "Rendering In Progress",
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+ this._moving = true;
+ // Get the url string from the chapter (may be from storage)
+ return chapter.render().
+ then(function(contents) {
+
+ // Unload the previous chapter listener
+ if(this.currentChapter) {
+ this.trigger("renderer:chapterUnload");
+ this.currentChapter.unload(); // Remove stored blobs
+
+ if(this.render.window){
+ this.render.window.removeEventListener("resize", this.resized);
+ }
+
+ this.removeEventListeners();
+ this.removeSelectionListeners();
+ this.trigger("renderer:chapterUnloaded");
+ this.contents = null;
+ this.doc = null;
+ this.pageMap = null;
+ }
+
+ this.currentChapter = chapter;
+
+ this.chapterPos = 1;
+
+ this.currentChapterCfiBase = chapter.cfiBase;
+
+ this.layoutSettings = this.reconcileLayoutSettings(globalLayout, chapter.properties);
+
+ return this.load(contents, chapter.href);
+
+ }.bind(this), function() {
+ this._moving = false;
+ }.bind(this));
+
+};
+
+/**
+* Loads a url (string) and renders it,
+* attaching event listeners and triggering hooks.
+* Returns: Promise with the rendered contents.
+*/
+
+EPUBJS.Renderer.prototype.load = function(contents, url){
+ var deferred = new RSVP.defer();
+ var loaded;
+
+ // Switch to the required layout method for the settings
+ this.layoutMethod = this.determineLayout(this.layoutSettings);
+ this.layout = new EPUBJS.Layout[this.layoutMethod]();
+
+ this.visible(false);
+
+ this.render.load(contents, url).then(function(contents) {
+
+ // Duck-type fixed layout books.
+ if (EPUBJS.Layout.isFixedLayout(contents)) {
+ this.layoutSettings.layout = "pre-paginated";
+ this.layoutMethod = this.determineLayout(this.layoutSettings);
+ this.layout = new EPUBJS.Layout[this.layoutMethod]();
+ }
+ this.render.setLayout(this.layoutSettings.layout);
+
+ // HTML element must have direction set if RTL or columnns will
+ // not be in the correct direction in Firefox
+ // Firefox also need the html element to be position right
+ if(this.render.direction == "rtl" && this.render.docEl.dir != "rtl"){
+ this.render.docEl.dir = "rtl";
+ if (this.render.layout !== "pre-paginated") {
+ this.render.docEl.style.position = "absolute";
+ this.render.docEl.style.right = "0";
+ }
+ }
+
+ this.afterLoad(contents);
+
+ //-- Trigger registered hooks before displaying
+ this.beforeDisplay(function(){
+
+ this.afterDisplay();
+
+ this.visible(true);
+
+
+ deferred.resolve(this); //-- why does this return the renderer?
+
+ }.bind(this));
+
+ }.bind(this));
+
+ return deferred.promise;
+};
+
+EPUBJS.Renderer.prototype.afterLoad = function(contents) {
+ var formated;
+ // this.currentChapter.setDocument(this.render.document);
+ this.contents = contents;
+ this.doc = this.render.document;
+
+ // Format the contents using the current layout method
+ this.formated = this.layout.format(contents, this.render.width, this.render.height, this.gap);
+ this.render.setPageDimensions(this.formated.pageWidth, this.formated.pageHeight);
+
+ // window.addEventListener("orientationchange", this.onResized.bind(this), false);
+ if(!this.initWidth && !this.initHeight){
+ this.render.window.addEventListener("resize", this.resized, false);
+ }
+
+ this.addEventListeners();
+ this.addSelectionListeners();
+
+};
+
+EPUBJS.Renderer.prototype.afterDisplay = function(contents) {
+
+ var pages = this.layout.calculatePages();
+ var msg = this.currentChapter;
+ var queued = this._q.length();
+ this._moving = false;
+
+ this.updatePages(pages);
+
+ this.visibleRangeCfi = this.getVisibleRangeCfi();
+ this.currentLocationCfi = this.visibleRangeCfi.start;
+
+ if(queued === 0) {
+ this.trigger("renderer:locationChanged", this.currentLocationCfi);
+ this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
+ }
+
+ msg.cfi = this.currentLocationCfi; //TODO: why is this cfi passed to chapterDisplayed
+ this.trigger("renderer:chapterDisplayed", msg);
+
+};
+
+EPUBJS.Renderer.prototype.loaded = function(url){
+ this.trigger("render:loaded", url);
+ // var uri = EPUBJS.core.uri(url);
+ // var relative = uri.path.replace(book.bookUrl, '');
+ // console.log(url, uri, relative);
+};
+
+/**
+* Reconciles the current chapters layout properies with
+* the global layout properities.
+* Takes: global layout settings object, chapter properties string
+* Returns: Object with layout properties
+*/
+EPUBJS.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){
+ var settings = {};
+
+ //-- Get the global defaults
+ for (var attr in global) {
+ if (global.hasOwnProperty(attr)){
+ settings[attr] = global[attr];
+ }
+ }
+ //-- Get the chapter's display type
+ chapter.forEach(function(prop){
+ var rendition = prop.replace("rendition:", '');
+ var split = rendition.indexOf("-");
+ var property, value;
+
+ if(split != -1){
+ property = rendition.slice(0, split);
+ value = rendition.slice(split+1);
+
+ settings[property] = value;
+ }
+ });
+ return settings;
+};
+
+/**
+* Uses the settings to determine which Layout Method is needed
+* Triggers events based on the method choosen
+* Takes: Layout settings object
+* Returns: String of appropriate for EPUBJS.Layout function
+*/
+EPUBJS.Renderer.prototype.determineLayout = function(settings){
+ // Default is layout: reflowable & spread: auto
+ var spreads = this.determineSpreads(this.minSpreadWidth);
+ var layoutMethod = spreads ? "ReflowableSpreads" : "Reflowable";
+ var scroll = false;
+
+ if(settings.layout === "pre-paginated") {
+ layoutMethod = "Fixed";
+ scroll = true;
+ spreads = false;
+ }
+
+ if(settings.layout === "reflowable" && settings.spread === "none") {
+ layoutMethod = "Reflowable";
+ scroll = false;
+ spreads = false;
+ }
+
+ if(settings.layout === "reflowable" && settings.spread === "both") {
+ layoutMethod = "ReflowableSpreads";
+ scroll = false;
+ spreads = true;
+ }
+
+ this.spreads = spreads;
+ this.render.scroll(scroll);
+ this.trigger("renderer:spreads", spreads);
+ return layoutMethod;
+};
+
+// Shortcut to trigger the hook before displaying the chapter
+EPUBJS.Renderer.prototype.beforeDisplay = function(callback, renderer){
+ this.triggerHooks("beforeChapterDisplay", callback, this);
+};
+
+// Update the renderer with the information passed by the layout
+EPUBJS.Renderer.prototype.updatePages = function(layout){
+ this.pageMap = this.mapPage();
+ // this.displayedPages = layout.displayedPages;
+
+ if (this.spreads) {
+ this.displayedPages = Math.ceil(this.pageMap.length / 2);
+ } else {
+ this.displayedPages = this.pageMap.length;
+ }
+
+ this.currentChapter.pages = this.pageMap.length;
+
+ this._q.flush();
+};
+
+// Apply the layout again and jump back to the previous cfi position
+EPUBJS.Renderer.prototype.reformat = function(){
+ var renderer = this;
+ var formated, pages;
+ var spreads;
+
+ if(!this.contents) return;
+
+ spreads = this.determineSpreads(this.minSpreadWidth);
+
+ // Only re-layout if the spreads have switched
+ if(spreads != this.spreads){
+ this.spreads = spreads;
+ this.layoutMethod = this.determineLayout(this.layoutSettings);
+ this.layout = new EPUBJS.Layout[this.layoutMethod]();
+ }
+
+ // Reset pages
+ this.chapterPos = 1;
+
+ this.render.page(this.chapterPos);
+ // Give the css styles time to update
+ // clearTimeout(this.timeoutTillCfi);
+ // this.timeoutTillCfi = setTimeout(function(){
+ renderer.formated = renderer.layout.format(renderer.render.docEl, renderer.render.width, renderer.render.height, renderer.gap);
+ renderer.render.setPageDimensions(renderer.formated.pageWidth, renderer.formated.pageHeight);
+
+ pages = renderer.layout.calculatePages();
+ renderer.updatePages(pages);
+
+ //-- Go to current page after formating
+ if(renderer.currentLocationCfi){
+ renderer.gotoCfi(renderer.currentLocationCfi);
+ }
+ // renderer.timeoutTillCfi = null;
+
+};
+
+// Hide and show the render's container .
+EPUBJS.Renderer.prototype.visible = function(bool){
+ if(typeof(bool) === "undefined") {
+ return this.element.style.visibility;
+ }
+
+ if(bool === true && !this.hidden){
+ this.element.style.visibility = "visible";
+ }else if(bool === false){
+ this.element.style.visibility = "hidden";
+ }
+};
+
+// Remove the render element and clean up listeners
+EPUBJS.Renderer.prototype.remove = function() {
+ if(this.render.window) {
+ this.render.unload();
+ this.render.window.removeEventListener("resize", this.resized);
+ this.removeEventListeners();
+ this.removeSelectionListeners();
+ }
+
+ // clean container content
+ this.container.innerHtml = "";
+// this.container.removeChild(this.element);
+};
+
+//-- STYLES
+
+EPUBJS.Renderer.prototype.applyStyles = function(styles) {
+ for (var style in styles) {
+ this.render.setStyle(style, styles[style]);
+ }
+};
+
+EPUBJS.Renderer.prototype.setStyle = function(style, val, prefixed){
+ this.render.setStyle(style, val, prefixed);
+};
+
+EPUBJS.Renderer.prototype.removeStyle = function(style){
+ this.render.removeStyle(style);
+};
+
+//-- HEAD TAGS
+EPUBJS.Renderer.prototype.applyHeadTags = function(headTags) {
+ for ( var headTag in headTags ) {
+ this.render.addHeadTag(headTag, headTags[headTag]);
+ }
+};
+
+//-- NAVIGATION
+
+EPUBJS.Renderer.prototype.page = function(pg){
+ if(!this.pageMap) {
+ console.warn("pageMap not set, queuing");
+ this._q.enqueue("page", arguments);
+ return true;
+ }
+
+ if(pg >= 1 && pg <= this.displayedPages){
+ this.chapterPos = pg;
+
+ this.render.page(pg);
+ this.visibleRangeCfi = this.getVisibleRangeCfi();
+ this.currentLocationCfi = this.visibleRangeCfi.start;
+ this.trigger("renderer:locationChanged", this.currentLocationCfi);
+ this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
+
+ return true;
+ }
+ //-- Return false if page is greater than the total
+ return false;
+};
+
+// Short cut to find next page's cfi starting at the last visible element
+/*
+EPUBJS.Renderer.prototype.nextPage = function(){
+ var pg = this.chapterPos + 1;
+ if(pg <= this.displayedPages){
+ this.chapterPos = pg;
+
+ this.render.page(pg);
+
+ this.currentLocationCfi = this.getPageCfi(this.visibileEl);
+ this.trigger("renderer:locationChanged", this.currentLocationCfi);
+
+ return true;
+ }
+ //-- Return false if page is greater than the total
+ return false;
+};
+*/
+EPUBJS.Renderer.prototype.nextPage = function(){
+ return this.page(this.chapterPos + 1);
+};
+
+EPUBJS.Renderer.prototype.prevPage = function(){
+ return this.page(this.chapterPos - 1);
+};
+
+//-- Show the page containing an Element
+EPUBJS.Renderer.prototype.pageByElement = function(el){
+ var pg;
+ if(!el) return;
+
+ pg = this.render.getPageNumberByElement(el);
+ this.page(pg);
+};
+
+// Jump to the last page of the chapter
+EPUBJS.Renderer.prototype.lastPage = function(){
+ if(this._moving) {
+ return this._q.enqueue("lastPage", arguments);
+ }
+
+ this.page(this.displayedPages);
+};
+
+// Jump to the first page of the chapter
+EPUBJS.Renderer.prototype.firstPage = function(){
+ if(this._moving) {
+ return this._q.enqueue("firstPage", arguments);
+ }
+
+ this.page(1);
+};
+
+//-- Find a section by fragement id
+EPUBJS.Renderer.prototype.section = function(fragment){
+ var el = this.doc.getElementById(fragment);
+
+ if(el){
+ this.pageByElement(el);
+ }
+
+};
+
+EPUBJS.Renderer.prototype.firstElementisTextNode = function(node) {
+ var children = node.childNodes;
+ var leng = children.length;
+
+ if(leng &&
+ children[0] && // First Child
+ children[0].nodeType === 3 && // This is a textNodes
+ children[0].textContent.trim().length) { // With non whitespace or return characters
+ return true;
+ }
+ return false;
+};
+
+EPUBJS.Renderer.prototype.isGoodNode = function(node) {
+ var embeddedElements = ["audio", "canvas", "embed", "iframe", "img", "math", "object", "svg", "video"];
+ if (embeddedElements.indexOf(node.tagName.toLowerCase()) !== -1) {
+ // Embedded elements usually do not have a text node as first element, but are also good nodes
+ return true;
+ }
+ return this.firstElementisTextNode(node);
+};
+
+// Walk the node tree from a start element to next visible element
+EPUBJS.Renderer.prototype.walk = function(node, x, y) {
+ var r, children, leng,
+ startNode = node,
+ prevNode,
+ stack = [startNode];
+
+ var STOP = 10000, ITER=0;
+
+ while(!r && stack.length) {
+ node = stack.shift();
+ if( this.containsPoint(node, x, y) && this.isGoodNode(node)) {
+ r = node;
+ }
+
+ if(!r && node && node.childElementCount > 0){
+ children = node.children;
+ if (children && children.length) {
+ leng = children.length ? children.length : 0;
+ } else {
+ return r;
+ }
+ for (var i = leng-1; i >= 0; i--) {
+ if(children[i] != prevNode) stack.unshift(children[i]);
+ }
+ }
+
+ if(!r && stack.length === 0 && startNode && startNode.parentNode !== null){
+ stack.push(startNode.parentNode);
+ prevNode = startNode;
+ startNode = startNode.parentNode;
+ }
+
+
+ ITER++;
+ if(ITER > STOP) {
+ console.error("ENDLESS LOOP");
+ break;
+ }
+
+ }
+
+ return r;
+};
+
+// Checks if an element is on the screen
+EPUBJS.Renderer.prototype.containsPoint = function(el, x, y){
+ var rect;
+ var left;
+ if(el && typeof el.getBoundingClientRect === 'function'){
+ rect = el.getBoundingClientRect();
+ // console.log(el, rect, x, y);
+
+ if( rect.width !== 0 &&
+ rect.height !== 0 && // Element not visible
+ rect.left >= x &&
+ x <= rect.left + rect.width) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+EPUBJS.Renderer.prototype.textSprint = function(root, func) {
+ var filterEmpty = function(node){
+ if ( ! /^\s*$/.test(node.data) ) {
+ return NodeFilter.FILTER_ACCEPT;
+ } else {
+ return NodeFilter.FILTER_REJECT;
+ }
+ };
+ var treeWalker;
+ var node;
+
+ try {
+ treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
+ acceptNode: filterEmpty
+ }, false);
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+ } catch (e) {
+ // IE doesn't accept the object, just wants a function
+ // https://msdn.microsoft.com/en-us/library/ff974820(v=vs.85).aspx
+ treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, filterEmpty, false);
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+ }
+
+
+
+};
+
+EPUBJS.Renderer.prototype.sprint = function(root, func) {
+ var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
+ var node;
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+
+};
+
+EPUBJS.Renderer.prototype.mapPage = function(){
+ var renderer = this;
+ var map = [];
+ var root = this.render.getBaseElement();
+ var page = 1;
+ var width = this.layout.colWidth + this.layout.gap;
+ var offset = this.formated.pageWidth * (this.chapterPos-1);
+ var limit = (width * page) - offset;// (width * page) - offset;
+ var elLimit = 0;
+ var prevRange;
+ var prevRanges;
+ var cfi;
+ var lastChildren = null;
+ var prevElement;
+ var startRange, endRange;
+ var startCfi, endCfi;
+ var check = function(node) {
+ var elPos;
+ var elRange;
+ var found;
+ if (node.nodeType == Node.TEXT_NODE) {
+
+ elRange = document.createRange();
+ elRange.selectNodeContents(node);
+ elPos = elRange.getBoundingClientRect();
+
+ if(!elPos || (elPos.width === 0 && elPos.height === 0)) {
+ return;
+ }
+
+ //-- Element starts new Col
+ if(elPos.left > elLimit) {
+ found = checkText(node);
+ }
+
+ //-- Element Spans new Col
+ if(elPos.right > elLimit) {
+ found = checkText(node);
+ }
+
+ prevElement = node;
+
+ if (found) {
+ prevRange = null;
+ }
+ }
+
+ };
+ var checkText = function(node){
+ var result;
+ var ranges = renderer.splitTextNodeIntoWordsRanges(node);
+
+ ranges.forEach(function(range){
+ var pos = range.getBoundingClientRect();
+
+ if(!pos || (pos.width === 0 && pos.height === 0)) {
+ return;
+ }
+ if(pos.left + pos.width < limit) {
+ if(!map[page-1]){
+ range.collapse(true);
+ cfi = renderer.currentChapter.cfiFromRange(range);
+ // map[page-1].start = cfi;
+ result = map.push({ start: cfi, end: null });
+ }
+ } else {
+ // Previous Range is null since we already found our last map pair
+ // Use that last walked textNode
+ if(!prevRange && prevElement) {
+ prevRanges = renderer.splitTextNodeIntoWordsRanges(prevElement);
+ prevRange = prevRanges[prevRanges.length-1];
+ }
+
+ if(prevRange && map.length){
+ prevRange.collapse(false);
+ cfi = renderer.currentChapter.cfiFromRange(prevRange);
+ if (map[map.length-1]) {
+ map[map.length-1].end = cfi;
+ }
+ }
+
+ range.collapse(true);
+ cfi = renderer.currentChapter.cfiFromRange(range);
+ result = map.push({
+ start: cfi,
+ end: null
+ });
+
+ page += 1;
+ limit = (width * page) - offset;
+ elLimit = limit;
+ }
+
+ prevRange = range;
+ });
+
+ return result;
+ };
+ var docEl = this.render.getDocumentElement();
+ var dir = docEl.dir;
+
+ // Set back to ltr before sprinting to get correct order
+ if(dir == "rtl") {
+ docEl.dir = "ltr";
+ if (this.layoutSettings.layout !== "pre-paginated") {
+ docEl.style.position = "static";
+ }
+ }
+
+ this.textSprint(root, check);
+
+ // Reset back to previous RTL settings
+ if(dir == "rtl") {
+ docEl.dir = dir;
+ if (this.layoutSettings.layout !== "pre-paginated") {
+ docEl.style.left = "auto";
+ docEl.style.right = "0";
+ }
+ }
+
+ // Check the remaining children that fit on this page
+ // to ensure the end is correctly calculated
+ if(!prevRange && prevElement) {
+ prevRanges = renderer.splitTextNodeIntoWordsRanges(prevElement);
+ prevRange = prevRanges[prevRanges.length-1];
+ }
+
+ if(prevRange){
+ prevRange.collapse(false);
+ cfi = renderer.currentChapter.cfiFromRange(prevRange);
+ map[map.length-1].end = cfi;
+ }
+
+ // Handle empty map
+ if(!map.length) {
+ startRange = this.doc.createRange();
+ startRange.selectNodeContents(root);
+ startRange.collapse(true);
+ startCfi = renderer.currentChapter.cfiFromRange(startRange);
+
+ endRange = this.doc.createRange();
+ endRange.selectNodeContents(root);
+ endRange.collapse(false);
+ endCfi = renderer.currentChapter.cfiFromRange(endRange);
+
+
+ map.push({ start: startCfi, end: endCfi });
+
+ }
+
+ // clean up
+ prevRange = null;
+ prevRanges = undefined;
+ startRange = null;
+ endRange = null;
+ root = null;
+
+ return map;
+};
+
+
+EPUBJS.Renderer.prototype.indexOfBreakableChar = function (text, startPosition) {
+ var whiteCharacters = "\x2D\x20\t\r\n\b\f";
+ // '-' \x2D
+ // ' ' \x20
+
+ if (! startPosition) {
+ startPosition = 0;
+ }
+
+ for (var i = startPosition; i < text.length; i++) {
+ if (whiteCharacters.indexOf(text.charAt(i)) != -1) {
+ return i;
+ }
+ }
+
+ return -1;
+};
+
+
+EPUBJS.Renderer.prototype.splitTextNodeIntoWordsRanges = function(node){
+ var ranges = [];
+ var text = node.textContent.trim();
+ var range;
+ var rect;
+ var list;
+
+ // Usage of indexOf() function for space character as word delimiter
+ // is not sufficient in case of other breakable characters like \r\n- etc
+ var pos = this.indexOfBreakableChar(text);
+
+ if(pos === -1) {
+ range = this.doc.createRange();
+ range.selectNodeContents(node);
+ return [range];
+ }
+
+ range = this.doc.createRange();
+ range.setStart(node, 0);
+ range.setEnd(node, pos);
+ ranges.push(range);
+
+ // there was a word miss in case of one letter words
+ range = this.doc.createRange();
+ range.setStart(node, pos+1);
+
+ while ( pos != -1 ) {
+
+ pos = this.indexOfBreakableChar(text, pos + 1);
+ if(pos > 0) {
+
+ if(range) {
+ range.setEnd(node, pos);
+ ranges.push(range);
+ }
+
+ range = this.doc.createRange();
+ range.setStart(node, pos+1);
+ }
+ }
+
+ if(range) {
+ range.setEnd(node, text.length);
+ ranges.push(range);
+ }
+
+ return ranges;
+};
+
+EPUBJS.Renderer.prototype.rangePosition = function(range){
+ var rect;
+ var list;
+
+ list = range.getClientRects();
+
+ if(list.length) {
+ rect = list[0];
+ return rect;
+ }
+
+ return null;
+};
+
+/*
+// Get the cfi of the current page
+EPUBJS.Renderer.prototype.getPageCfi = function(prevEl){
+ var range = this.doc.createRange();
+ var position;
+ // TODO : this might need to take margin / padding into account?
+ var x = 1;//this.formated.pageWidth/2;
+ var y = 1;//;this.formated.pageHeight/2;
+
+ range = this.getRange(x, y);
+
+ // var test = this.doc.defaultView.getSelection();
+ // var r = this.doc.createRange();
+ // test.removeAllRanges();
+ // r.setStart(range.startContainer, range.startOffset);
+ // r.setEnd(range.startContainer, range.startOffset + 1);
+ // test.addRange(r);
+
+ return this.currentChapter.cfiFromRange(range);
+};
+*/
+
+// Get the cfi of the current page
+EPUBJS.Renderer.prototype.getPageCfi = function(){
+ var pg = (this.chapterPos * 2)-1;
+ return this.pageMap[pg].start;
+};
+
+EPUBJS.Renderer.prototype.getRange = function(x, y, forceElement){
+ var range = this.doc.createRange();
+ var position;
+ forceElement = true; // temp override
+ if(typeof document.caretPositionFromPoint !== "undefined" && !forceElement){
+ position = this.doc.caretPositionFromPoint(x, y);
+ range.setStart(position.offsetNode, position.offset);
+ } else if(typeof document.caretRangeFromPoint !== "undefined" && !forceElement){
+ range = this.doc.caretRangeFromPoint(x, y);
+ } else {
+ this.visibileEl = this.findElementAfter(x, y);
+ range.setStart(this.visibileEl, 1);
+ }
+
+ // var test = this.doc.defaultView.getSelection();
+ // var r = this.doc.createRange();
+ // test.removeAllRanges();
+ // r.setStart(range.startContainer, range.startOffset);
+ // r.setEnd(range.startContainer, range.startOffset + 1);
+ // test.addRange(r);
+ return range;
+};
+
+/*
+EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(prevEl){
+ var startX = 0;
+ var startY = 0;
+ var endX = this.width-1;
+ var endY = this.height-1;
+ var startRange = this.getRange(startX, startY);
+ var endRange = this.getRange(endX, endY); //fix if carret not avail
+ var startCfi = this.currentChapter.cfiFromRange(startRange);
+ var endCfi;
+ if(endRange) {
+ endCfi = this.currentChapter.cfiFromRange(endRange);
+ }
+
+ return {
+ start: startCfi,
+ end: endCfi || false
+ };
+};
+*/
+
+EPUBJS.Renderer.prototype.pagesInCurrentChapter = function() {
+ var pgs;
+ var length;
+
+ if(!this.pageMap) {
+ console.warn("page map not loaded");
+ return false;
+ }
+
+ length = this.pageMap.length;
+
+ // if(this.spreads){
+ // pgs = Math.ceil(length / 2);
+ // } else {
+ // pgs = length;
+ // }
+
+ return length;
+};
+
+EPUBJS.Renderer.prototype.currentRenderedPage = function(){
+ var pg;
+
+ if(!this.pageMap) {
+ console.warn("page map not loaded");
+ return false;
+ }
+
+ if (this.spreads && this.pageMap.length > 1) {
+ pg = (this.chapterPos*2) - 1;
+ } else {
+ pg = this.chapterPos;
+ }
+
+ return pg;
+};
+
+EPUBJS.Renderer.prototype.getRenderedPagesLeft = function(){
+ var pg;
+ var lastPage;
+ var pagesLeft;
+
+ if(!this.pageMap) {
+ console.warn("page map not loaded");
+ return false;
+ }
+
+ lastPage = this.pageMap.length;
+
+ if (this.spreads) {
+ pg = (this.chapterPos*2) - 1;
+ } else {
+ pg = this.chapterPos;
+ }
+
+ pagesLeft = lastPage - pg;
+ return pagesLeft;
+
+};
+
+EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(){
+ var pg;
+ var startRange, endRange;
+
+ if(!this.pageMap) {
+ console.warn("page map not loaded");
+ return false;
+ }
+
+ if (this.spreads) {
+ pg = this.chapterPos*2;
+ startRange = this.pageMap[pg-2];
+ endRange = startRange;
+
+ if(this.pageMap.length > 1 && this.pageMap.length > pg-1) {
+ endRange = this.pageMap[pg-1];
+ }
+ } else {
+ pg = this.chapterPos;
+ startRange = this.pageMap[pg-1];
+ endRange = startRange;
+ }
+
+ if(!startRange) {
+ console.warn("page range miss:", pg, this.pageMap);
+ startRange = this.pageMap[this.pageMap.length-1];
+ endRange = startRange;
+ }
+
+ return {
+ start: startRange.start,
+ end: endRange.end
+ };
+};
+
+// Goto a cfi position in the current chapter
+EPUBJS.Renderer.prototype.gotoCfi = function(cfi){
+ var pg;
+ var marker;
+ var range;
+
+ if(this._moving){
+ return this._q.enqueue("gotoCfi", arguments);
+ }
+
+ if(EPUBJS.core.isString(cfi)){
+ cfi = this.epubcfi.parse(cfi);
+ }
+
+ if(typeof document.evaluate === 'undefined') {
+ marker = this.epubcfi.addMarker(cfi, this.doc);
+ if(marker) {
+ pg = this.render.getPageNumberByElement(marker);
+ // Must Clean up Marker before going to page
+ this.epubcfi.removeMarker(marker, this.doc);
+ this.page(pg);
+ }
+ } else {
+ range = this.epubcfi.generateRangeFromCfi(cfi, this.doc);
+ if(range) {
+ // It seems that sometimes getBoundingClientRect() returns null for first page CFI in chapter.
+ // It is always reproductible if few consecutive chapters have only one page.
+ // NOTE: This is only workaround and the issue needs an deeper investigation.
+ // NOTE: Observed on Android 4.2.1 using WebView widget as HTML renderer (Asus TF300T).
+ var rect = range.getBoundingClientRect();
+ if (rect) {
+ pg = this.render.getPageNumberByRect(rect);
+
+ } else {
+ // Goto first page in chapter
+ pg = 1;
+ }
+
+ this.page(pg);
+
+ // Reset the current location cfi to requested cfi
+ this.currentLocationCfi = cfi.str;
+ } else {
+ // Failed to find a range, go to first page
+ this.page(1);
+ }
+ }
+};
+
+// Walk nodes until a visible element is found
+EPUBJS.Renderer.prototype.findFirstVisible = function(startEl){
+ var el = startEl || this.render.getBaseElement();
+ var found;
+ // Looks like an old API usage
+ // Set x and y as 0 to fullfill walk method API.
+ found = this.walk(el, 0, 0);
+
+ if(found) {
+ return found;
+ }else{
+ return startEl;
+ }
+
+};
+// TODO: remove me - unsused
+EPUBJS.Renderer.prototype.findElementAfter = function(x, y, startEl){
+ var el = startEl || this.render.getBaseElement();
+ var found;
+ found = this.walk(el, x, y);
+ if(found) {
+ return found;
+ }else{
+ return el;
+ }
+
+};
+
+/*
+EPUBJS.Renderer.prototype.route = function(hash, callback){
+ var location = window.location.hash.replace('#/', '');
+ if(this.useHash && location.length && location != this.prevLocation){
+ this.show(location, callback);
+ this.prevLocation = location;
+ return true;
+ }
+ return false;
+}
+
+EPUBJS.Renderer.prototype.hideHashChanges = function(){
+ this.useHash = false;
+}
+
+*/
+
+EPUBJS.Renderer.prototype.resize = function(width, height, setSize){
+ var spreads;
+
+ this.width = width;
+ this.height = height;
+
+ if(setSize !== false) {
+ this.render.resize(this.width, this.height);
+ }
+
+
+
+ if(this.contents){
+ this.reformat();
+ }
+
+ this.trigger("renderer:resized", {
+ width: this.width,
+ height: this.height
+ });
+};
+
+//-- Listeners for events in the frame
+
+EPUBJS.Renderer.prototype.onResized = function(e) {
+ this.trigger('renderer:beforeResize');
+
+ var width = this.container.clientWidth;
+ var height = this.container.clientHeight;
+
+ this.resize(width, height, false);
+};
+
+EPUBJS.Renderer.prototype.addEventListeners = function(){
+ if(!this.render.document) {
+ return;
+ }
+ this.listenedEvents.forEach(function(eventName){
+ this.render.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
+ }, this);
+
+};
+
+EPUBJS.Renderer.prototype.removeEventListeners = function(){
+ if(!this.render.document) {
+ return;
+ }
+ this.listenedEvents.forEach(function(eventName){
+ this.render.document.removeEventListener(eventName, this.triggerEvent, false);
+ }, this);
+
+};
+
+// Pass browser events
+EPUBJS.Renderer.prototype.triggerEvent = function(e){
+ this.trigger("renderer:"+e.type, e);
+};
+
+EPUBJS.Renderer.prototype.addSelectionListeners = function(){
+ this.render.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
+};
+
+EPUBJS.Renderer.prototype.removeSelectionListeners = function(){
+ if(!this.render.document) {
+ return;
+ }
+ this.doc.removeEventListener("selectionchange", this.onSelectionChange, false);
+};
+
+EPUBJS.Renderer.prototype.onSelectionChange = function(e){
+ if (this.selectionEndTimeout) {
+ clearTimeout(this.selectionEndTimeout);
+ }
+ this.selectionEndTimeout = setTimeout(function() {
+ this.selectedRange = this.render.window.getSelection();
+ this.trigger("renderer:selected", this.selectedRange);
+ }.bind(this), 500);
+};
+
+
+//-- Spreads
+
+EPUBJS.Renderer.prototype.setMinSpreadWidth = function(width){
+ this.minSpreadWidth = width;
+ this.spreads = this.determineSpreads(width);
+};
+
+EPUBJS.Renderer.prototype.determineSpreads = function(cutoff){
+ if(this.isForcedSingle || !cutoff || this.width < cutoff) {
+ return false; //-- Single Page
+ }else{
+ return true; //-- Double Page
+ }
+};
+
+EPUBJS.Renderer.prototype.forceSingle = function(bool){
+ if(bool) {
+ this.isForcedSingle = true;
+ // this.spreads = false;
+ } else {
+ this.isForcedSingle = false;
+ // this.spreads = this.determineSpreads(this.minSpreadWidth);
+ }
+};
+
+EPUBJS.Renderer.prototype.setGap = function(gap){
+ this.gap = gap; //-- False == auto gap
+};
+
+EPUBJS.Renderer.prototype.setDirection = function(direction){
+ this.direction = direction;
+ this.render.setDirection(this.direction);
+};
+
+//-- Content Replacements
+
+EPUBJS.Renderer.prototype.replace = function(query, func, finished, progress){
+ var items = this.contents.querySelectorAll(query),
+ resources = Array.prototype.slice.call(items),
+ count = resources.length;
+
+
+ if(count === 0) {
+ finished(false);
+ return;
+ }
+ resources.forEach(function(item){
+ var called = false;
+ var after = function(result, full){
+ if(called === false) {
+ count--;
+ if(progress) progress(result, full, count);
+ if(count <= 0 && finished) finished(true);
+ called = true;
+ }
+ };
+
+ func(item, after);
+
+ }.bind(this));
+
+};
+
+//-- Enable binding events to Renderer
+RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype);
+
+var EPUBJS = EPUBJS || {};
+EPUBJS.replace = {};
+
+//-- Replaces the relative links within the book to use our internal page changer
+EPUBJS.replace.hrefs = function(callback, renderer){
+ var book = this;
+ var replacments = function(link, done){
+ var href = link.getAttribute("href"),
+ isRelative = href.search("://"),
+ directory,
+ relative,
+ location,
+ base,
+ uri,
+ url;
+
+ if(isRelative != -1){
+
+ link.setAttribute("target", "_blank");
+
+ }else{
+ // Links may need to be resolved, such as ../chp1.xhtml
+ base = renderer.render.docEl.querySelector('base');
+ url = base.getAttribute("href");
+ uri = EPUBJS.core.uri(url);
+ directory = uri.directory;
+
+ if(directory) {
+ // We must ensure that the file:// protocol is preserved for
+ // local file links, as in certain contexts (such as under
+ // Titanium), file links without the file:// protocol will not
+ // work
+ if (uri.protocol === "file") {
+ relative = EPUBJS.core.resolveUrl(uri.base, href);
+ } else {
+ relative = EPUBJS.core.resolveUrl(directory, href);
+ }
+ } else {
+ relative = href;
+ }
+
+ link.onclick = function(){
+ book.goto(relative);
+ return false;
+ };
+
+ }
+ done();
+
+ };
+
+ renderer.replace("a[href]", replacments, callback);
+
+};
+
+EPUBJS.replace.head = function(callback, renderer) {
+
+ renderer.replaceWithStored("link[href]", "href", EPUBJS.replace.links, callback);
+
+};
+
+
+//-- Replaces assets src's to point to stored version if browser is offline
+EPUBJS.replace.resources = function(callback, renderer){
+ //srcs = this.doc.querySelectorAll('[src]');
+ renderer.replaceWithStored("[src]", "src", EPUBJS.replace.srcs, callback);
+
+};
+
+EPUBJS.replace.svg = function(callback, renderer) {
+
+ renderer.replaceWithStored("svg image", "xlink:href", function(_store, full, done){
+ _store.getUrl(full).then(done);
+ }, callback);
+
+};
+
+EPUBJS.replace.srcs = function(_store, full, done){
+
+ _store.getUrl(full).then(done);
+
+};
+
+//-- Replaces links in head, such as stylesheets - link[href]
+EPUBJS.replace.links = function(_store, full, done, link){
+ //-- Handle replacing urls in CSS
+ if(link.getAttribute("rel") === "stylesheet") {
+ EPUBJS.replace.stylesheets(_store, full).then(function(url, full){
+ // done
+ done(url, full);
+ }, function(reason) {
+ // we were unable to replace the style sheets
+ done(null);
+ });
+ }else{
+ _store.getUrl(full).then(done, function(reason) {
+ // we were unable to get the url, signal to upper layer
+ done(null);
+ });
+ }
+};
+
+EPUBJS.replace.stylesheets = function(_store, full) {
+ var deferred = new RSVP.defer();
+
+ if(!_store) return;
+
+ _store.getText(full).then(function(text){
+ var url;
+
+ EPUBJS.replace.cssImports(_store, full, text).then(function (importText) {
+
+ text = importText + text;
+
+ EPUBJS.replace.cssUrls(_store, full, text).then(function(newText){
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+
+ var blob = new Blob([newText], { "type" : "text\/css" }),
+ url = _URL.createObjectURL(blob);
+
+ deferred.resolve(url);
+
+ }, function(reason) {
+ deferred.reject(reason);
+ });
+
+ },function(reason) {
+ deferred.reject(reason);
+ });
+
+ }, function(reason) {
+ deferred.reject(reason);
+ });
+
+ return deferred.promise;
+};
+
+EPUBJS.replace.cssImports = function (_store, base, text) {
+ var deferred = new RSVP.defer();
+ if(!_store) return;
+
+ // check for css @import
+ var importRegex = /@import\s+(?:url\()?\'?\"?((?!data:)[^\'|^\"^\)]*)\'?\"?\)?/gi;
+ var importMatches, importFiles = [], allImportText = '';
+
+ while (importMatches = importRegex.exec(text)) {
+ importFiles.push(importMatches[1]);
+ }
+
+ if (importFiles.length === 0) {
+ deferred.resolve(allImportText);
+ }
+
+ importFiles.forEach(function (fileUrl) {
+ var full = EPUBJS.core.resolveUrl(base, fileUrl);
+ full = EPUBJS.core.uri(full).path;
+ _store.getText(full).then(function(importText){
+ allImportText += importText;
+ if (importFiles.indexOf(fileUrl) === importFiles.length - 1) {
+ deferred.resolve(allImportText);
+ }
+ }, function(reason) {
+ deferred.reject(reason);
+ });
+ });
+
+ return deferred.promise;
+
+};
+
+
+EPUBJS.replace.cssUrls = function(_store, base, text){
+ var deferred = new RSVP.defer(),
+ promises = [],
+ matches = text.match(/url\(\'?\"?((?!data:)[^\'|^\"^\)]*)\'?\"?\)/g);
+
+ if(!_store) return;
+
+ if(!matches){
+ deferred.resolve(text);
+ return deferred.promise;
+ }
+
+ matches.forEach(function(str){
+ var full = EPUBJS.core.resolveUrl(base, str.replace(/url\(|[|\)|\'|\"]|\?.*$/g, ''));
+ var replaced = _store.getUrl(full).then(function(url){
+ text = text.replace(str, 'url("'+url+'")');
+ }, function(reason) {
+ deferred.reject(reason);
+ });
+
+ promises.push(replaced);
+ });
+
+ RSVP.all(promises).then(function(){
+ deferred.resolve(text);
+ });
+
+ return deferred.promise;
+};
+
+
+EPUBJS.Storage = function(withCredentials){
+
+ this.checkRequirements();
+ this.urlCache = {};
+ this.withCredentials = withCredentials;
+ this.URL = window.URL || window.webkitURL || window.mozURL;
+ this.offline = false;
+};
+
+//-- Load the zip lib and set the workerScriptsPath
+EPUBJS.Storage.prototype.checkRequirements = function(callback){
+ if(typeof(localforage) == "undefined") console.error("localForage library not loaded");
+};
+
+EPUBJS.Storage.prototype.put = function(assets, store) {
+ var deferred = new RSVP.defer();
+ var count = assets.length;
+ var current = 0;
+ var next = function(deferred){
+ var done = deferred || new RSVP.defer();
+ var url;
+ var encodedUrl;
+
+ if(current >= count) {
+ done.resolve();
+ } else {
+ url = assets[current].url;
+ encodedUrl = window.encodeURIComponent(url);
+
+ EPUBJS.core.request(url, "binary")
+ .then(function (data) {
+ return localforage.setItem(encodedUrl, data);
+ })
+ .then(function(data){
+ current++;
+ // Load up the next
+ setTimeout(function(){
+ next(done);
+ }, 1);
+
+ });
+ }
+ return done.promise;
+ }.bind(this);
+
+ if(!Array.isArray(assets)) {
+ assets = [assets];
+ }
+
+ next().then(function(){
+ deferred.resolve();
+ }.bind(this));
+
+ return deferred.promise;
+};
+
+EPUBJS.Storage.prototype.token = function(url, value){
+ var encodedUrl = window.encodeURIComponent(url);
+ return localforage.setItem(encodedUrl, value)
+ .then(function (result) {
+ if (result === null) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+};
+
+EPUBJS.Storage.prototype.isStored = function(url){
+ var encodedUrl = window.encodeURIComponent(url);
+ return localforage.getItem(encodedUrl)
+ .then(function (result) {
+ if (result === null) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+};
+
+EPUBJS.Storage.prototype.getText = function(url){
+ var encodedUrl = window.encodeURIComponent(url);
+
+ return EPUBJS.core.request(url, 'arraybuffer', this.withCredentials)
+ .then(function(buffer){
+
+ if(this.offline){
+ this.offline = false;
+ this.trigger("offline", false);
+ }
+ localforage.setItem(encodedUrl, buffer);
+ return buffer;
+ }.bind(this))
+ .then(function(data) {
+ var deferred = new RSVP.defer();
+ var mimeType = EPUBJS.core.getMimeType(url);
+ var blob = new Blob([data], {type : mimeType});
+ var reader = new FileReader();
+ reader.addEventListener("loadend", function() {
+ deferred.resolve(reader.result);
+ });
+ reader.readAsText(blob, mimeType);
+ return deferred.promise;
+ })
+ .catch(function() {
+
+ var deferred = new RSVP.defer();
+ var entry = localforage.getItem(encodedUrl);
+
+ if(!this.offline){
+ this.offline = true;
+ this.trigger("offline", true);
+ }
+
+ if(!entry) {
+ deferred.reject({
+ message : "File not found in the storage: " + url,
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ entry.then(function(data) {
+ var mimeType = EPUBJS.core.getMimeType(url);
+ var blob = new Blob([data], {type : mimeType});
+ var reader = new FileReader();
+ reader.addEventListener("loadend", function() {
+ deferred.resolve(reader.result);
+ });
+ reader.readAsText(blob, mimeType);
+ });
+
+ return deferred.promise;
+ }.bind(this));
+};
+
+EPUBJS.Storage.prototype.getUrl = function(url){
+ var encodedUrl = window.encodeURIComponent(url);
+
+ return EPUBJS.core.request(url, 'arraybuffer', this.withCredentials)
+ .then(function(buffer){
+ if(this.offline){
+ this.offline = false;
+ this.trigger("offline", false);
+ }
+ localforage.setItem(encodedUrl, buffer);
+ return url;
+ }.bind(this))
+ .catch(function() {
+ var deferred = new RSVP.defer();
+ var entry;
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var tempUrl;
+
+ if(!this.offline){
+ this.offline = true;
+ this.trigger("offline", true);
+ }
+
+ if(encodedUrl in this.urlCache) {
+ deferred.resolve(this.urlCache[encodedUrl]);
+ return deferred.promise;
+ }
+
+ entry = localforage.getItem(encodedUrl);
+
+ if(!entry) {
+ deferred.reject({
+ message : "File not found in the storage: " + url,
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ entry.then(function(data) {
+ var blob = new Blob([data], {type : EPUBJS.core.getMimeType(url)});
+ tempUrl = _URL.createObjectURL(blob);
+ deferred.resolve(tempUrl);
+ this.urlCache[encodedUrl] = tempUrl;
+ }.bind(this));
+
+
+ return deferred.promise;
+ }.bind(this));
+};
+
+EPUBJS.Storage.prototype.getXml = function(url){
+ var encodedUrl = window.encodeURIComponent(url);
+
+ return EPUBJS.core.request(url, 'arraybuffer', this.withCredentials)
+ .then(function(buffer){
+ if(this.offline){
+ this.offline = false;
+ this.trigger("offline", false);
+ }
+ localforage.setItem(encodedUrl, buffer);
+ return buffer;
+ }.bind(this))
+ .then(function(data) {
+ var deferred = new RSVP.defer();
+ var mimeType = EPUBJS.core.getMimeType(url);
+ var blob = new Blob([data], {type : mimeType});
+ var reader = new FileReader();
+ reader.addEventListener("loadend", function() {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(reader.result, "text/xml");
+ deferred.resolve(doc);
+ });
+ reader.readAsText(blob, mimeType);
+ return deferred.promise;
+ })
+ .catch(function() {
+ var deferred = new RSVP.defer();
+ var entry = localforage.getItem(encodedUrl);
+
+ if(!this.offline){
+ this.offline = true;
+ this.trigger("offline", true);
+ }
+
+ if(!entry) {
+ deferred.reject({
+ message : "File not found in the storage: " + url,
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ entry.then(function(data) {
+ var mimeType = EPUBJS.core.getMimeType(url);
+ var blob = new Blob([data], {type : mimeType});
+ var reader = new FileReader();
+ reader.addEventListener("loadend", function() {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(reader.result, "text/xml");
+ deferred.resolve(doc);
+ });
+ reader.readAsText(blob, mimeType);
+ });
+
+ return deferred.promise;
+ }.bind(this));
+};
+
+EPUBJS.Storage.prototype.revokeUrl = function(url){
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var fromCache = this.urlCache[url];
+ if(fromCache) _URL.revokeObjectURL(fromCache);
+};
+
+EPUBJS.Storage.prototype.failed = function(error){
+ console.error(error);
+};
+
+RSVP.EventTarget.mixin(EPUBJS.Storage.prototype);
+
+EPUBJS.Unarchiver = function(url){
+
+ this.checkRequirements();
+ this.urlCache = {};
+
+};
+
+//-- Load the zip lib and set the workerScriptsPath
+EPUBJS.Unarchiver.prototype.checkRequirements = function(callback){
+ if(typeof(JSZip) == "undefined") console.error("JSZip lib not loaded");
+};
+
+EPUBJS.Unarchiver.prototype.open = function(zipUrl, callback){
+ if (zipUrl instanceof ArrayBuffer) {
+ this.zip = new JSZip(zipUrl);
+ var deferred = new RSVP.defer();
+ deferred.resolve();
+ return deferred.promise;
+ } else {
+ return EPUBJS.core.request(zipUrl, "binary").then(function(data){
+ this.zip = new JSZip(data);
+ }.bind(this));
+ }
+};
+
+EPUBJS.Unarchiver.prototype.getXml = function(url, encoding){
+ var decodededUrl = window.decodeURIComponent(url);
+ return this.getText(decodededUrl, encoding).
+ then(function(text){
+ var parser = new DOMParser();
+ var mimeType = EPUBJS.core.getMimeType(url);
+ return parser.parseFromString(text, mimeType);
+ });
+
+};
+
+EPUBJS.Unarchiver.prototype.getUrl = function(url, mime){
+ var unarchiver = this;
+ var deferred = new RSVP.defer();
+ var decodededUrl = window.decodeURIComponent(url);
+ var entry = this.zip.file(decodededUrl);
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var tempUrl;
+ var blob;
+
+ if(!entry) {
+ deferred.reject({
+ message : "File not found in the epub: " + url,
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ if(url in this.urlCache) {
+ deferred.resolve(this.urlCache[url]);
+ return deferred.promise;
+ }
+
+ blob = new Blob([entry.asUint8Array()], {type : EPUBJS.core.getMimeType(entry.name)});
+
+ tempUrl = _URL.createObjectURL(blob);
+ deferred.resolve(tempUrl);
+ unarchiver.urlCache[url] = tempUrl;
+
+ return deferred.promise;
+};
+
+EPUBJS.Unarchiver.prototype.getText = function(url, encoding){
+ var unarchiver = this;
+ var deferred = new RSVP.defer();
+ var decodededUrl = window.decodeURIComponent(url);
+ var entry = this.zip.file(decodededUrl);
+ var text;
+
+ if(!entry) {
+ deferred.reject({
+ message : "File not found in the epub: " + url,
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ text = entry.asText();
+ deferred.resolve(text);
+
+ return deferred.promise;
+};
+
+EPUBJS.Unarchiver.prototype.revokeUrl = function(url){
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var fromCache = this.urlCache[url];
+ if(fromCache) _URL.revokeObjectURL(fromCache);
+};
+
+EPUBJS.Unarchiver.prototype.failed = function(error){
+ console.error(error);
+};
+
+EPUBJS.Unarchiver.prototype.afterSaved = function(error){
+ this.callback();
+};
+
+EPUBJS.Unarchiver.prototype.toStorage = function(entries){
+ var timeout = 0,
+ delay = 20,
+ that = this,
+ count = entries.length;
+
+ function callback(){
+ count--;
+ if(count === 0) that.afterSaved();
+ }
+
+ entries.forEach(function(entry){
+
+ setTimeout(function(entry){
+ that.saveEntryFileToStorage(entry, callback);
+ }, timeout, entry);
+
+ timeout += delay;
+ });
+
+ console.log("time", timeout);
+
+ //entries.forEach(this.saveEntryFileToStorage.bind(this));
+};
+
+// EPUBJS.Unarchiver.prototype.saveEntryFileToStorage = function(entry, callback){
+// var that = this;
+// entry.getData(new zip.BlobWriter(), function(blob) {
+// EPUBJS.storage.save(entry.filename, blob, callback);
+// });
+// };
+
+/*
+ From Zip.js, by Gildas Lormeau
+ */
+
+(function() {
+ "use strict";
+ var table = {
+ "application" : {
+ "ecmascript" : [ "es", "ecma" ],
+ "javascript" : "js",
+ "ogg" : "ogx",
+ "pdf" : "pdf",
+ "postscript" : [ "ps", "ai", "eps", "epsi", "epsf", "eps2", "eps3" ],
+ "rdf+xml" : "rdf",
+ "smil" : [ "smi", "smil" ],
+ "xhtml+xml" : [ "xhtml", "xht" ],
+ "xml" : [ "xml", "xsl", "xsd", "opf", "ncx" ],
+ "zip" : "zip",
+ "x-httpd-eruby" : "rhtml",
+ "x-latex" : "latex",
+ "x-maker" : [ "frm", "maker", "frame", "fm", "fb", "book", "fbdoc" ],
+ "x-object" : "o",
+ "x-shockwave-flash" : [ "swf", "swfl" ],
+ "x-silverlight" : "scr",
+ "epub+zip" : "epub",
+ "font-tdpfr" : "pfr",
+ "inkml+xml" : [ "ink", "inkml" ],
+ "json" : "json",
+ "jsonml+json" : "jsonml",
+ "mathml+xml" : "mathml",
+ "metalink+xml" : "metalink",
+ "mp4" : "mp4s",
+ // "oebps-package+xml" : "opf",
+ "omdoc+xml" : "omdoc",
+ "oxps" : "oxps",
+ "vnd.amazon.ebook" : "azw",
+ "widget" : "wgt",
+ // "x-dtbncx+xml" : "ncx",
+ "x-dtbook+xml" : "dtb",
+ "x-dtbresource+xml" : "res",
+ "x-font-bdf" : "bdf",
+ "x-font-ghostscript" : "gsf",
+ "x-font-linux-psf" : "psf",
+ "x-font-otf" : "otf",
+ "x-font-pcf" : "pcf",
+ "x-font-snf" : "snf",
+ "x-font-ttf" : [ "ttf", "ttc" ],
+ "x-font-type1" : [ "pfa", "pfb", "pfm", "afm" ],
+ "x-font-woff" : "woff",
+ "x-mobipocket-ebook" : [ "prc", "mobi" ],
+ "x-mspublisher" : "pub",
+ "x-nzb" : "nzb",
+ "x-tgif" : "obj",
+ "xaml+xml" : "xaml",
+ "xml-dtd" : "dtd",
+ "xproc+xml" : "xpl",
+ "xslt+xml" : "xslt",
+ "internet-property-stream" : "acx",
+ "x-compress" : "z",
+ "x-compressed" : "tgz",
+ "x-gzip" : "gz",
+ },
+ "audio" : {
+ "flac" : "flac",
+ "midi" : [ "mid", "midi", "kar", "rmi" ],
+ "mpeg" : [ "mpga", "mpega", "mp2", "mp3", "m4a", "mp2a", "m2a", "m3a" ],
+ "mpegurl" : "m3u",
+ "ogg" : [ "oga", "ogg", "spx" ],
+ "x-aiff" : [ "aif", "aiff", "aifc" ],
+ "x-ms-wma" : "wma",
+ "x-wav" : "wav",
+ "adpcm" : "adp",
+ "mp4" : "mp4a",
+ "webm" : "weba",
+ "x-aac" : "aac",
+ "x-caf" : "caf",
+ "x-matroska" : "mka",
+ "x-pn-realaudio-plugin" : "rmp",
+ "xm" : "xm",
+ "mid" : [ "mid", "rmi" ]
+ },
+ "image" : {
+ "gif" : "gif",
+ "ief" : "ief",
+ "jpeg" : [ "jpeg", "jpg", "jpe" ],
+ "pcx" : "pcx",
+ "png" : "png",
+ "svg+xml" : [ "svg", "svgz" ],
+ "tiff" : [ "tiff", "tif" ],
+ "x-icon" : "ico",
+ "bmp" : "bmp",
+ "webp" : "webp",
+ "x-pict" : [ "pic", "pct" ],
+ "x-tga" : "tga",
+ "cis-cod" : "cod",
+ },
+ "message" : {
+ "rfc822" : [ "eml", "mime", "mht", "mhtml", "nws" ]
+ },
+ "text" : {
+ "cache-manifest" : [ "manifest", "appcache" ],
+ "calendar" : [ "ics", "icz", "ifb" ],
+ "css" : "css",
+ "csv" : "csv",
+ "h323" : "323",
+ "html" : [ "html", "htm", "shtml", "stm" ],
+ "iuls" : "uls",
+ "mathml" : "mml",
+ "plain" : [ "txt", "text", "brf", "conf", "def", "list", "log", "in", "bas" ],
+ "richtext" : "rtx",
+ "tab-separated-values" : "tsv",
+ "x-bibtex" : "bib",
+ "x-dsrc" : "d",
+ "x-diff" : [ "diff", "patch" ],
+ "x-haskell" : "hs",
+ "x-java" : "java",
+ "x-literate-haskell" : "lhs",
+ "x-moc" : "moc",
+ "x-pascal" : [ "p", "pas" ],
+ "x-pcs-gcd" : "gcd",
+ "x-perl" : [ "pl", "pm" ],
+ "x-python" : "py",
+ "x-scala" : "scala",
+ "x-setext" : "etx",
+ "x-tcl" : [ "tcl", "tk" ],
+ "x-tex" : [ "tex", "ltx", "sty", "cls" ],
+ "x-vcard" : "vcf",
+ "sgml" : [ "sgml", "sgm" ],
+ "x-c" : [ "c", "cc", "cxx", "cpp", "h", "hh", "dic" ],
+ "x-fortran" : [ "f", "for", "f77", "f90" ],
+ "x-opml" : "opml",
+ "x-nfo" : "nfo",
+ "x-sfv" : "sfv",
+ "x-uuencode" : "uu",
+ "webviewhtml" : "htt"
+ },
+ "video" : {
+ "mpeg" : [ "mpeg", "mpg", "mpe", "m1v", "m2v", "mp2", "mpa", "mpv2" ],
+ "mp4" : [ "mp4", "mp4v", "mpg4" ],
+ "quicktime" : [ "qt", "mov" ],
+ "ogg" : "ogv",
+ "vnd.mpegurl" : [ "mxu", "m4u" ],
+ "x-flv" : "flv",
+ "x-la-asf" : [ "lsf", "lsx" ],
+ "x-mng" : "mng",
+ "x-ms-asf" : [ "asf", "asx", "asr" ],
+ "x-ms-wm" : "wm",
+ "x-ms-wmv" : "wmv",
+ "x-ms-wmx" : "wmx",
+ "x-ms-wvx" : "wvx",
+ "x-msvideo" : "avi",
+ "x-sgi-movie" : "movie",
+ "x-matroska" : [ "mpv", "mkv", "mk3d", "mks" ],
+ "3gpp2" : "3g2",
+ "h261" : "h261",
+ "h263" : "h263",
+ "h264" : "h264",
+ "jpeg" : "jpgv",
+ "jpm" : [ "jpm", "jpgm" ],
+ "mj2" : [ "mj2", "mjp2" ],
+ "vnd.ms-playready.media.pyv" : "pyv",
+ "vnd.uvvu.mp4" : [ "uvu", "uvvu" ],
+ "vnd.vivo" : "viv",
+ "webm" : "webm",
+ "x-f4v" : "f4v",
+ "x-m4v" : "m4v",
+ "x-ms-vob" : "vob",
+ "x-smv" : "smv"
+ }
+ };
+
+ var mimeTypes = (function() {
+ var type, subtype, val, index, mimeTypes = {};
+ for (type in table) {
+ if (table.hasOwnProperty(type)) {
+ for (subtype in table[type]) {
+ if (table[type].hasOwnProperty(subtype)) {
+ val = table[type][subtype];
+ if (typeof val == "string") {
+ mimeTypes[val] = type + "/" + subtype;
+ } else {
+ for (index = 0; index < val.length; index++) {
+ mimeTypes[val[index]] = type + "/" + subtype;
+ }
+ }
+ }
+ }
+ }
+ }
+ return mimeTypes;
+ })();
+
+ EPUBJS.core.getMimeType = function(filename) {
+ var defaultValue = "text/plain";//"application/octet-stream";
+ return filename && mimeTypes[filename.split(".").pop().toLowerCase()] || defaultValue;
+ };
+
+})();
+
+//# sourceMappingURL=epub.js.map \ No newline at end of file