summaryrefslogtreecommitdiff
path: root/lib/epub.js/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'lib/epub.js/src/utils')
-rw-r--r--lib/epub.js/src/utils/constants.js62
-rw-r--r--lib/epub.js/src/utils/core.js876
-rw-r--r--lib/epub.js/src/utils/hook.js82
-rw-r--r--lib/epub.js/src/utils/mime.js169
-rw-r--r--lib/epub.js/src/utils/path.js102
-rw-r--r--lib/epub.js/src/utils/queue.js246
-rw-r--r--lib/epub.js/src/utils/replacements.js138
-rw-r--r--lib/epub.js/src/utils/request.js150
-rw-r--r--lib/epub.js/src/utils/scrolltype.js55
-rw-r--r--lib/epub.js/src/utils/url.js108
10 files changed, 1988 insertions, 0 deletions
diff --git a/lib/epub.js/src/utils/constants.js b/lib/epub.js/src/utils/constants.js
new file mode 100644
index 0000000..ac0a268
--- /dev/null
+++ b/lib/epub.js/src/utils/constants.js
@@ -0,0 +1,62 @@
+export const EPUBJS_VERSION = "0.3";
+
+// Dom events to listen for
+export const DOM_EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "mousemove", "click", "touchend", "touchstart", "touchmove"];
+
+export const EVENTS = {
+ BOOK : {
+ OPEN_FAILED : "openFailed"
+ },
+ CONTENTS : {
+ EXPAND : "expand",
+ RESIZE : "resize",
+ SELECTED : "selected",
+ SELECTED_RANGE : "selectedRange",
+ LINK_CLICKED : "linkClicked"
+ },
+ LOCATIONS : {
+ CHANGED : "changed"
+ },
+ MANAGERS : {
+ RESIZE : "resize",
+ RESIZED : "resized",
+ ORIENTATION_CHANGE : "orientationchange",
+ ADDED : "added",
+ SCROLL : "scroll",
+ SCROLLED : "scrolled",
+ REMOVED : "removed",
+ },
+ VIEWS : {
+ AXIS: "axis",
+ WRITING_MODE: "writingMode",
+ LOAD_ERROR : "loaderror",
+ RENDERED : "rendered",
+ RESIZED : "resized",
+ DISPLAYED : "displayed",
+ SHOWN : "shown",
+ HIDDEN : "hidden",
+ MARK_CLICKED : "markClicked"
+ },
+ RENDITION : {
+ STARTED : "started",
+ ATTACHED : "attached",
+ DISPLAYED : "displayed",
+ DISPLAY_ERROR : "displayerror",
+ RENDERED : "rendered",
+ REMOVED : "removed",
+ RESIZED : "resized",
+ ORIENTATION_CHANGE : "orientationchange",
+ LOCATION_CHANGED : "locationChanged",
+ RELOCATED : "relocated",
+ MARK_CLICKED : "markClicked",
+ SELECTED : "selected",
+ LAYOUT: "layout"
+ },
+ LAYOUT : {
+ UPDATED : "updated"
+ },
+ ANNOTATION : {
+ ATTACH : "attach",
+ DETACH : "detach"
+ }
+}
diff --git a/lib/epub.js/src/utils/core.js b/lib/epub.js/src/utils/core.js
new file mode 100644
index 0000000..5c83944
--- /dev/null
+++ b/lib/epub.js/src/utils/core.js
@@ -0,0 +1,876 @@
+/**
+ * Core Utilities and Helpers
+ * @module Core
+*/
+import { DOMParser as XMLDOMParser } from "xmldom";
+
+/**
+ * Vendor prefixed requestAnimationFrame
+ * @returns {function} requestAnimationFrame
+ * @memberof Core
+ */
+export const requestAnimationFrame = (typeof window != "undefined") ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
+const ELEMENT_NODE = 1;
+const TEXT_NODE = 3;
+const COMMENT_NODE = 8;
+const DOCUMENT_NODE = 9;
+const _URL = typeof URL != "undefined" ? URL : (typeof window != "undefined" ? (window.URL || window.webkitURL || window.mozURL) : undefined);
+
+/**
+ * Generates a UUID
+ * based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
+ * @returns {string} uuid
+ * @memberof Core
+ */
+export function uuid() {
+ 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;
+}
+
+/**
+ * Gets the height of a document
+ * @returns {number} height
+ * @memberof Core
+ */
+export function documentHeight() {
+ return Math.max(
+ document.documentElement.clientHeight,
+ document.body.scrollHeight,
+ document.documentElement.scrollHeight,
+ document.body.offsetHeight,
+ document.documentElement.offsetHeight
+ );
+}
+
+/**
+ * Checks if a node is an element
+ * @param {object} obj
+ * @returns {boolean}
+ * @memberof Core
+ */
+export function isElement(obj) {
+ return !!(obj && obj.nodeType == 1);
+}
+
+/**
+ * @param {any} n
+ * @returns {boolean}
+ * @memberof Core
+ */
+export function isNumber(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+/**
+ * @param {any} n
+ * @returns {boolean}
+ * @memberof Core
+ */
+export function isFloat(n) {
+ let f = parseFloat(n);
+
+ if (isNumber(n) === false) {
+ return false;
+ }
+
+ if (typeof n === "string" && n.indexOf(".") > -1) {
+ return true;
+ }
+
+ return Math.floor(f) !== f;
+}
+
+/**
+ * Get a prefixed css property
+ * @param {string} unprefixed
+ * @returns {string}
+ * @memberof Core
+ */
+export function prefixed(unprefixed) {
+ var vendors = ["Webkit", "webkit", "Moz", "O", "ms" ];
+ var prefixes = ["-webkit-", "-webkit-", "-moz-", "-o-", "-ms-"];
+ var lower = unprefixed.toLowerCase();
+ var length = vendors.length;
+
+ if (typeof(document) === "undefined" || typeof(document.body.style[lower]) != "undefined") {
+ return unprefixed;
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (typeof(document.body.style[prefixes[i] + lower]) != "undefined") {
+ return prefixes[i] + lower;
+ }
+ }
+
+ return unprefixed;
+}
+
+/**
+ * Apply defaults to an object
+ * @param {object} obj
+ * @returns {object}
+ * @memberof Core
+ */
+export function defaults(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;
+}
+
+/**
+ * Extend properties of an object
+ * @param {object} target
+ * @returns {object}
+ * @memberof Core
+ */
+export function extend(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;
+}
+
+/**
+ * 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
+ * @param {any} item
+ * @param {array} array
+ * @param {function} [compareFunction]
+ * @returns {number} location (in array)
+ * @memberof Core
+ */
+export function insert(item, array, compareFunction) {
+ var location = locationOf(item, array, compareFunction);
+ array.splice(location, 0, item);
+
+ return location;
+}
+
+/**
+ * Finds where something would fit into a sorted array
+ * @param {any} item
+ * @param {array} array
+ * @param {function} [compareFunction]
+ * @param {function} [_start]
+ * @param {function} [_end]
+ * @returns {number} location (in array)
+ * @memberof Core
+ */
+export function locationOf(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 locationOf(item, array, compareFunction, pivot, end);
+ } else{
+ return locationOf(item, array, compareFunction, start, pivot);
+ }
+}
+
+/**
+ * Finds index of something in a sorted array
+ * Returns -1 if not found
+ * @param {any} item
+ * @param {array} array
+ * @param {function} [compareFunction]
+ * @param {function} [_start]
+ * @param {function} [_end]
+ * @returns {number} index (in array) or -1
+ * @memberof Core
+ */
+export function indexOfSorted(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 indexOfSorted(item, array, compareFunction, pivot, end);
+ } else{
+ return indexOfSorted(item, array, compareFunction, start, pivot);
+ }
+}
+/**
+ * Find the bounds of an element
+ * taking padding and margin into account
+ * @param {element} el
+ * @returns {{ width: Number, height: Number}}
+ * @memberof Core
+ */
+export function bounds(el) {
+
+ var style = window.getComputedStyle(el);
+ var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
+ var heightProps = ["height", "paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
+
+ var width = 0;
+ var height = 0;
+
+ widthProps.forEach(function(prop){
+ width += parseFloat(style[prop]) || 0;
+ });
+
+ heightProps.forEach(function(prop){
+ height += parseFloat(style[prop]) || 0;
+ });
+
+ return {
+ height: height,
+ width: width
+ };
+
+}
+
+/**
+ * Find the bounds of an element
+ * taking padding, margin and borders into account
+ * @param {element} el
+ * @returns {{ width: Number, height: Number}}
+ * @memberof Core
+ */
+export function borders(el) {
+
+ var style = window.getComputedStyle(el);
+ var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
+ var heightProps = ["paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
+
+ var width = 0;
+ var height = 0;
+
+ widthProps.forEach(function(prop){
+ width += parseFloat(style[prop]) || 0;
+ });
+
+ heightProps.forEach(function(prop){
+ height += parseFloat(style[prop]) || 0;
+ });
+
+ return {
+ height: height,
+ width: width
+ };
+
+}
+
+/**
+ * Find the bounds of any node
+ * allows for getting bounds of text nodes by wrapping them in a range
+ * @param {node} node
+ * @returns {BoundingClientRect}
+ * @memberof Core
+ */
+export function nodeBounds(node) {
+ let elPos;
+ let doc = node.ownerDocument;
+ if(node.nodeType == Node.TEXT_NODE){
+ let elRange = doc.createRange();
+ elRange.selectNodeContents(node);
+ elPos = elRange.getBoundingClientRect();
+ } else {
+ elPos = node.getBoundingClientRect();
+ }
+ return elPos;
+}
+
+/**
+ * Find the equivelent of getBoundingClientRect of a browser window
+ * @returns {{ width: Number, height: Number, top: Number, left: Number, right: Number, bottom: Number }}
+ * @memberof Core
+ */
+export function windowBounds() {
+
+ var width = window.innerWidth;
+ var height = window.innerHeight;
+
+ return {
+ top: 0,
+ left: 0,
+ right: width,
+ bottom: height,
+ width: width,
+ height: height
+ };
+
+}
+
+/**
+ * Gets the index of a node in its parent
+ * @param {Node} node
+ * @param {string} typeId
+ * @return {number} index
+ * @memberof Core
+ */
+export function indexOfNode(node, typeId) {
+ var parent = node.parentNode;
+ var children = parent.childNodes;
+ var sib;
+ var index = -1;
+ for (var i = 0; i < children.length; i++) {
+ sib = children[i];
+ if (sib.nodeType === typeId) {
+ index++;
+ }
+ if (sib == node) break;
+ }
+
+ return index;
+}
+
+/**
+ * Gets the index of a text node in its parent
+ * @param {node} textNode
+ * @returns {number} index
+ * @memberof Core
+ */
+export function indexOfTextNode(textNode) {
+ return indexOfNode(textNode, TEXT_NODE);
+}
+
+/**
+ * Gets the index of an element node in its parent
+ * @param {element} elementNode
+ * @returns {number} index
+ * @memberof Core
+ */
+export function indexOfElementNode(elementNode) {
+ return indexOfNode(elementNode, ELEMENT_NODE);
+}
+
+/**
+ * Check if extension is xml
+ * @param {string} ext
+ * @returns {boolean}
+ * @memberof Core
+ */
+export function isXml(ext) {
+ return ["xml", "opf", "ncx"].indexOf(ext) > -1;
+}
+
+/**
+ * Create a new blob
+ * @param {any} content
+ * @param {string} mime
+ * @returns {Blob}
+ * @memberof Core
+ */
+export function createBlob(content, mime){
+ return new Blob([content], {type : mime });
+}
+
+/**
+ * Create a new blob url
+ * @param {any} content
+ * @param {string} mime
+ * @returns {string} url
+ * @memberof Core
+ */
+export function createBlobUrl(content, mime){
+ var tempUrl;
+ var blob = createBlob(content, mime);
+
+ tempUrl = _URL.createObjectURL(blob);
+
+ return tempUrl;
+}
+
+/**
+ * Remove a blob url
+ * @param {string} url
+ * @memberof Core
+ */
+export function revokeBlobUrl(url){
+ return _URL.revokeObjectURL(url);
+}
+
+/**
+ * Create a new base64 encoded url
+ * @param {any} content
+ * @param {string} mime
+ * @returns {string} url
+ * @memberof Core
+ */
+export function createBase64Url(content, mime){
+ var data;
+ var datauri;
+
+ if (typeof(content) !== "string") {
+ // Only handles strings
+ return;
+ }
+
+ data = btoa(encodeURIComponent(content));
+
+ datauri = "data:" + mime + ";base64," + data;
+
+ return datauri;
+}
+
+/**
+ * Get type of an object
+ * @param {object} obj
+ * @returns {string} type
+ * @memberof Core
+ */
+export function type(obj){
+ return Object.prototype.toString.call(obj).slice(8, -1);
+}
+
+/**
+ * Parse xml (or html) markup
+ * @param {string} markup
+ * @param {string} mime
+ * @param {boolean} forceXMLDom force using xmlDom to parse instead of native parser
+ * @returns {document} document
+ * @memberof Core
+ */
+export function parse(markup, mime, forceXMLDom) {
+ var doc;
+ var Parser;
+
+ if (typeof DOMParser === "undefined" || forceXMLDom) {
+ Parser = XMLDOMParser;
+ } else {
+ Parser = DOMParser;
+ }
+
+ // Remove byte order mark before parsing
+ // https://www.w3.org/International/questions/qa-byte-order-mark
+ if(markup.charCodeAt(0) === 0xFEFF) {
+ markup = markup.slice(1);
+ }
+
+ doc = new Parser().parseFromString(markup, mime);
+
+ return doc;
+}
+
+/**
+ * querySelector polyfill
+ * @param {element} el
+ * @param {string} sel selector string
+ * @returns {element} element
+ * @memberof Core
+ */
+export function qs(el, sel) {
+ var elements;
+ if (!el) {
+ throw new Error("No Element Provided");
+ }
+
+ if (typeof el.querySelector != "undefined") {
+ return el.querySelector(sel);
+ } else {
+ elements = el.getElementsByTagName(sel);
+ if (elements.length) {
+ return elements[0];
+ }
+ }
+}
+
+/**
+ * querySelectorAll polyfill
+ * @param {element} el
+ * @param {string} sel selector string
+ * @returns {element[]} elements
+ * @memberof Core
+ */
+export function qsa(el, sel) {
+
+ if (typeof el.querySelector != "undefined") {
+ return el.querySelectorAll(sel);
+ } else {
+ return el.getElementsByTagName(sel);
+ }
+}
+
+/**
+ * querySelector by property
+ * @param {element} el
+ * @param {string} sel selector string
+ * @param {object[]} props
+ * @returns {element[]} elements
+ * @memberof Core
+ */
+export function qsp(el, sel, props) {
+ var q, filtered;
+ if (typeof el.querySelector != "undefined") {
+ sel += "[";
+ for (var prop in props) {
+ sel += prop + "~='" + props[prop] + "'";
+ }
+ sel += "]";
+ return el.querySelector(sel);
+ } else {
+ q = el.getElementsByTagName(sel);
+ filtered = Array.prototype.slice.call(q, 0).filter(function(el) {
+ for (var prop in props) {
+ if(el.getAttribute(prop) === props[prop]){
+ return true;
+ }
+ }
+ return false;
+ });
+
+ if (filtered) {
+ return filtered[0];
+ }
+ }
+}
+
+/**
+ * Sprint through all text nodes in a document
+ * @memberof Core
+ * @param {element} root element to start with
+ * @param {function} func function to run on each element
+ */
+export function sprint(root, func) {
+ var doc = root.ownerDocument || root;
+ if (typeof(doc.createTreeWalker) !== "undefined") {
+ treeWalker(root, func, NodeFilter.SHOW_TEXT);
+ } else {
+ walk(root, function(node) {
+ if (node && node.nodeType === 3) { // Node.TEXT_NODE
+ func(node);
+ }
+ }, true);
+ }
+}
+
+/**
+ * Create a treeWalker
+ * @memberof Core
+ * @param {element} root element to start with
+ * @param {function} func function to run on each element
+ * @param {function | object} filter funtion or object to filter with
+ */
+export function treeWalker(root, func, filter) {
+ var treeWalker = document.createTreeWalker(root, filter, null, false);
+ let node;
+ while ((node = treeWalker.nextNode())) {
+ func(node);
+ }
+}
+
+/**
+ * @memberof Core
+ * @param {node} node
+ * @param {callback} return false for continue,true for break inside callback
+ */
+export function walk(node,callback){
+ if(callback(node)){
+ return true;
+ }
+ node = node.firstChild;
+ if(node){
+ do{
+ let walked = walk(node,callback);
+ if(walked){
+ return true;
+ }
+ node = node.nextSibling;
+ } while(node);
+ }
+}
+
+/**
+ * Convert a blob to a base64 encoded string
+ * @param {Blog} blob
+ * @returns {string}
+ * @memberof Core
+ */
+export function blob2base64(blob) {
+ return new Promise(function(resolve, reject) {
+ var reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function() {
+ resolve(reader.result);
+ };
+ });
+}
+
+
+/**
+ * Creates a new pending promise and provides methods to resolve or reject it.
+ * From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
+ * @memberof Core
+ */
+export function defer() {
+ /* A method to resolve the associated Promise with the value passed.
+ * If the promise is already settled it does nothing.
+ *
+ * @param {anything} value : This value is used to resolve the promise
+ * If the value is a Promise then the associated promise assumes the state
+ * of Promise passed as value.
+ */
+ this.resolve = null;
+
+ /* A method to reject the assocaited Promise with the value passed.
+ * If the promise is already settled it does nothing.
+ *
+ * @param {anything} reason: The reason for the rejection of the Promise.
+ * Generally its an Error object. If however a Promise is passed, then the Promise
+ * itself will be the reason for rejection no matter the state of the Promise.
+ */
+ this.reject = null;
+
+ this.id = uuid();
+
+ /* A newly created Pomise object.
+ * Initially in pending state.
+ */
+ this.promise = new Promise((resolve, reject) => {
+ this.resolve = resolve;
+ this.reject = reject;
+ });
+ Object.freeze(this);
+}
+
+/**
+ * querySelector with filter by epub type
+ * @param {element} html
+ * @param {string} element element type to find
+ * @param {string} type epub type to find
+ * @returns {element[]} elements
+ * @memberof Core
+ */
+export function querySelectorByType(html, element, type){
+ var query;
+ if (typeof html.querySelector != "undefined") {
+ query = html.querySelector(`${element}[*|type="${type}"]`);
+ }
+ // Handle IE not supporting namespaced epub:type in querySelector
+ if(!query || query.length === 0) {
+ query = qsa(html, element);
+ for (var i = 0; i < query.length; i++) {
+ if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type ||
+ query[i].getAttribute("epub:type") === type) {
+ return query[i];
+ }
+ }
+ } else {
+ return query;
+ }
+}
+
+/**
+ * Find direct decendents of an element
+ * @param {element} el
+ * @returns {element[]} children
+ * @memberof Core
+ */
+export function findChildren(el) {
+ var result = [];
+ var childNodes = el.childNodes;
+ for (var i = 0; i < childNodes.length; i++) {
+ let node = childNodes[i];
+ if (node.nodeType === 1) {
+ result.push(node);
+ }
+ }
+ return result;
+}
+
+/**
+ * Find all parents (ancestors) of an element
+ * @param {element} node
+ * @returns {element[]} parents
+ * @memberof Core
+ */
+export function parents(node) {
+ var nodes = [node];
+ for (; node; node = node.parentNode) {
+ nodes.unshift(node);
+ }
+ return nodes
+}
+
+/**
+ * Find all direct decendents of a specific type
+ * @param {element} el
+ * @param {string} nodeName
+ * @param {boolean} [single]
+ * @returns {element[]} children
+ * @memberof Core
+ */
+export function filterChildren(el, nodeName, single) {
+ var result = [];
+ var childNodes = el.childNodes;
+ for (var i = 0; i < childNodes.length; i++) {
+ let node = childNodes[i];
+ if (node.nodeType === 1 && node.nodeName.toLowerCase() === nodeName) {
+ if (single) {
+ return node;
+ } else {
+ result.push(node);
+ }
+ }
+ }
+ if (!single) {
+ return result;
+ }
+}
+
+/**
+ * Filter all parents (ancestors) with tag name
+ * @param {element} node
+ * @param {string} tagname
+ * @returns {element[]} parents
+ * @memberof Core
+ */
+export function getParentByTagName(node, tagname) {
+ let parent;
+ if (node === null || tagname === '') return;
+ parent = node.parentNode;
+ while (parent.nodeType === 1) {
+ if (parent.tagName.toLowerCase() === tagname) {
+ return parent;
+ }
+ parent = parent.parentNode;
+ }
+}
+
+/**
+ * Lightweight Polyfill for DOM Range
+ * @class
+ * @memberof Core
+ */
+export class RangeObject {
+ constructor() {
+ this.collapsed = false;
+ this.commonAncestorContainer = undefined;
+ this.endContainer = undefined;
+ this.endOffset = undefined;
+ this.startContainer = undefined;
+ this.startOffset = undefined;
+ }
+
+ setStart(startNode, startOffset) {
+ this.startContainer = startNode;
+ this.startOffset = startOffset;
+
+ if (!this.endContainer) {
+ this.collapse(true);
+ } else {
+ this.commonAncestorContainer = this._commonAncestorContainer();
+ }
+
+ this._checkCollapsed();
+ }
+
+ setEnd(endNode, endOffset) {
+ this.endContainer = endNode;
+ this.endOffset = endOffset;
+
+ if (!this.startContainer) {
+ this.collapse(false);
+ } else {
+ this.collapsed = false;
+ this.commonAncestorContainer = this._commonAncestorContainer();
+ }
+
+ this._checkCollapsed();
+ }
+
+ collapse(toStart) {
+ this.collapsed = true;
+ if (toStart) {
+ this.endContainer = this.startContainer;
+ this.endOffset = this.startOffset;
+ this.commonAncestorContainer = this.startContainer.parentNode;
+ } else {
+ this.startContainer = this.endContainer;
+ this.startOffset = this.endOffset;
+ this.commonAncestorContainer = this.endOffset.parentNode;
+ }
+ }
+
+ selectNode(referenceNode) {
+ let parent = referenceNode.parentNode;
+ let index = Array.prototype.indexOf.call(parent.childNodes, referenceNode);
+ this.setStart(parent, index);
+ this.setEnd(parent, index + 1);
+ }
+
+ selectNodeContents(referenceNode) {
+ let end = referenceNode.childNodes[referenceNode.childNodes - 1];
+ let endIndex = (referenceNode.nodeType === 3) ?
+ referenceNode.textContent.length : parent.childNodes.length;
+ this.setStart(referenceNode, 0);
+ this.setEnd(referenceNode, endIndex);
+ }
+
+ _commonAncestorContainer(startContainer, endContainer) {
+ var startParents = parents(startContainer || this.startContainer);
+ var endParents = parents(endContainer || this.endContainer);
+
+ if (startParents[0] != endParents[0]) return undefined;
+
+ for (var i = 0; i < startParents.length; i++) {
+ if (startParents[i] != endParents[i]) {
+ return startParents[i - 1];
+ }
+ }
+ }
+
+ _checkCollapsed() {
+ if (this.startContainer === this.endContainer &&
+ this.startOffset === this.endOffset) {
+ this.collapsed = true;
+ } else {
+ this.collapsed = false;
+ }
+ }
+
+ toString() {
+ // TODO: implement walking between start and end to find text
+ }
+}
diff --git a/lib/epub.js/src/utils/hook.js b/lib/epub.js/src/utils/hook.js
new file mode 100644
index 0000000..ea1b901
--- /dev/null
+++ b/lib/epub.js/src/utils/hook.js
@@ -0,0 +1,82 @@
+/**
+ * Hooks allow for injecting functions that must all complete in order before finishing
+ * They will execute in parallel but all must finish before continuing
+ * Functions may return a promise if they are asycn.
+ * @param {any} context scope of this
+ * @example this.content = new EPUBJS.Hook(this);
+ */
+class Hook {
+ constructor(context){
+ this.context = context || this;
+ this.hooks = [];
+ }
+
+ /**
+ * Adds a function to be run before a hook completes
+ * @example this.content.register(function(){...});
+ */
+ register(){
+ for(var i = 0; i < arguments.length; ++i) {
+ if (typeof arguments[i] === "function") {
+ this.hooks.push(arguments[i]);
+ } else {
+ // unpack array
+ for(var j = 0; j < arguments[i].length; ++j) {
+ this.hooks.push(arguments[i][j]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a function
+ * @example this.content.deregister(function(){...});
+ */
+ deregister(func){
+ let hook;
+ for (let i = 0; i < this.hooks.length; i++) {
+ hook = this.hooks[i];
+ if (hook === func) {
+ this.hooks.splice(i, 1);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Triggers a hook to run all functions
+ * @example this.content.trigger(args).then(function(){...});
+ */
+ trigger(){
+ var args = arguments;
+ var context = this.context;
+ var promises = [];
+
+ this.hooks.forEach(function(task) {
+ try {
+ var executing = task.apply(context, args);
+ } catch (err) {
+ console.log(err);
+ }
+
+ if(executing && typeof executing["then"] === "function") {
+ // Task is a function that returns a promise
+ promises.push(executing);
+ }
+ // Otherwise Task resolves immediately, continue
+ });
+
+
+ return Promise.all(promises);
+ }
+
+ // Adds a function to be run before a hook completes
+ list(){
+ return this.hooks;
+ }
+
+ clear(){
+ return this.hooks = [];
+ }
+}
+export default Hook;
diff --git a/lib/epub.js/src/utils/mime.js b/lib/epub.js/src/utils/mime.js
new file mode 100644
index 0000000..8f4cca3
--- /dev/null
+++ b/lib/epub.js/src/utils/mime.js
@@ -0,0 +1,169 @@
+/*
+ From Zip.js, by Gildas Lormeau
+edited down
+ */
+
+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"
+ },
+ "text" : {
+ "cache-manifest" : [ "manifest", "appcache" ],
+ "css" : "css",
+ "csv" : "csv",
+ "html" : [ "html", "htm", "shtml", "stm" ],
+ "mathml" : "mml",
+ "plain" : [ "txt", "text", "brf", "conf", "def", "list", "log", "in", "bas" ],
+ "richtext" : "rtx",
+ "tab-separated-values" : "tsv",
+ "x-bibtex" : "bib"
+ },
+ "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;
+})();
+
+var defaultValue = "text/plain";//"application/octet-stream";
+
+function lookup(filename) {
+ return filename && mimeTypes[filename.split(".").pop().toLowerCase()] || defaultValue;
+};
+
+export default { lookup };
diff --git a/lib/epub.js/src/utils/path.js b/lib/epub.js/src/utils/path.js
new file mode 100644
index 0000000..6a060cb
--- /dev/null
+++ b/lib/epub.js/src/utils/path.js
@@ -0,0 +1,102 @@
+import path from "path-webpack";
+
+/**
+ * Creates a Path object for parsing and manipulation of a path strings
+ *
+ * Uses a polyfill for Nodejs path: https://nodejs.org/api/path.html
+ * @param {string} pathString a url string (relative or absolute)
+ * @class
+ */
+class Path {
+ constructor(pathString) {
+ var protocol;
+ var parsed;
+
+ protocol = pathString.indexOf("://");
+ if (protocol > -1) {
+ pathString = new URL(pathString).pathname;
+ }
+
+ parsed = this.parse(pathString);
+
+ this.path = pathString;
+
+ if (this.isDirectory(pathString)) {
+ this.directory = pathString;
+ } else {
+ this.directory = parsed.dir + "/";
+ }
+
+ this.filename = parsed.base;
+ this.extension = parsed.ext.slice(1);
+
+ }
+
+ /**
+ * Parse the path: https://nodejs.org/api/path.html#path_path_parse_path
+ * @param {string} what
+ * @returns {object}
+ */
+ parse (what) {
+ return path.parse(what);
+ }
+
+ /**
+ * @param {string} what
+ * @returns {boolean}
+ */
+ isAbsolute (what) {
+ return path.isAbsolute(what || this.path);
+ }
+
+ /**
+ * Check if path ends with a directory
+ * @param {string} what
+ * @returns {boolean}
+ */
+ isDirectory (what) {
+ return (what.charAt(what.length-1) === "/");
+ }
+
+ /**
+ * Resolve a path against the directory of the Path
+ *
+ * https://nodejs.org/api/path.html#path_path_resolve_paths
+ * @param {string} what
+ * @returns {string} resolved
+ */
+ resolve (what) {
+ return path.resolve(this.directory, what);
+ }
+
+ /**
+ * Resolve a path relative to the directory of the Path
+ *
+ * https://nodejs.org/api/path.html#path_path_relative_from_to
+ * @param {string} what
+ * @returns {string} relative
+ */
+ relative (what) {
+ var isAbsolute = what && (what.indexOf("://") > -1);
+
+ if (isAbsolute) {
+ return what;
+ }
+
+ return path.relative(this.directory, what);
+ }
+
+ splitPath(filename) {
+ return this.splitPathRe.exec(filename).slice(1);
+ }
+
+ /**
+ * Return the path string
+ * @returns {string} path
+ */
+ toString () {
+ return this.path;
+ }
+}
+
+export default Path;
diff --git a/lib/epub.js/src/utils/queue.js b/lib/epub.js/src/utils/queue.js
new file mode 100644
index 0000000..1f8a18a
--- /dev/null
+++ b/lib/epub.js/src/utils/queue.js
@@ -0,0 +1,246 @@
+import {defer, requestAnimationFrame} from "./core";
+
+/**
+ * Queue for handling tasks one at a time
+ * @class
+ * @param {scope} context what this will resolve to in the tasks
+ */
+class Queue {
+ constructor(context){
+ this._q = [];
+ this.context = context;
+ this.tick = requestAnimationFrame;
+ this.running = false;
+ this.paused = false;
+ }
+
+ /**
+ * Add an item to the queue
+ * @return {Promise}
+ */
+ enqueue() {
+ var deferred, promise;
+ var queued;
+ var task = [].shift.call(arguments);
+ var args = arguments;
+
+ // Handle single args without context
+ // if(args && !Array.isArray(args)) {
+ // args = [args];
+ // }
+ if(!task) {
+ throw new Error("No Task Provided");
+ }
+
+ if(typeof task === "function"){
+
+ deferred = new defer();
+ promise = deferred.promise;
+
+ queued = {
+ "task" : task,
+ "args" : args,
+ //"context" : context,
+ "deferred" : deferred,
+ "promise" : promise
+ };
+
+ } else {
+ // Task is a promise
+ queued = {
+ "promise" : task
+ };
+
+ }
+
+ this._q.push(queued);
+
+ // Wait to start queue flush
+ if (this.paused == false && !this.running) {
+ // setTimeout(this.flush.bind(this), 0);
+ // this.tick.call(window, this.run.bind(this));
+ this.run();
+ }
+
+ return queued.promise;
+ }
+
+ /**
+ * Run one item
+ * @return {Promise}
+ */
+ dequeue(){
+ var inwait, task, result;
+
+ if(this._q.length && !this.paused) {
+ inwait = this._q.shift();
+ task = inwait.task;
+ if(task){
+ // console.log(task)
+
+ result = task.apply(this.context, inwait.args);
+
+ if(result && typeof result["then"] === "function") {
+ // Task is a function that returns a promise
+ return result.then(function(){
+ inwait.deferred.resolve.apply(this.context, arguments);
+ }.bind(this), function() {
+ inwait.deferred.reject.apply(this.context, arguments);
+ }.bind(this));
+ } else {
+ // Task resolves immediately
+ inwait.deferred.resolve.apply(this.context, result);
+ return inwait.promise;
+ }
+
+
+
+ } else if(inwait.promise) {
+ // Task is a promise
+ return inwait.promise;
+ }
+
+ } else {
+ inwait = new defer();
+ inwait.deferred.resolve();
+ return inwait.promise;
+ }
+
+ }
+
+ // Run All Immediately
+ dump(){
+ while(this._q.length) {
+ this.dequeue();
+ }
+ }
+
+ /**
+ * Run all tasks sequentially, at convince
+ * @return {Promise}
+ */
+ run(){
+
+ if(!this.running){
+ this.running = true;
+ this.defered = new defer();
+ }
+
+ this.tick.call(window, () => {
+
+ if(this._q.length) {
+
+ this.dequeue()
+ .then(function(){
+ this.run();
+ }.bind(this));
+
+ } else {
+ this.defered.resolve();
+ this.running = undefined;
+ }
+
+ });
+
+ // Unpause
+ if(this.paused == true) {
+ this.paused = false;
+ }
+
+ return this.defered.promise;
+ }
+
+ /**
+ * Flush all, as quickly as possible
+ * @return {Promise}
+ */
+ flush(){
+
+ if(this.running){
+ return this.running;
+ }
+
+ if(this._q.length) {
+ this.running = this.dequeue()
+ .then(function(){
+ this.running = undefined;
+ return this.flush();
+ }.bind(this));
+
+ return this.running;
+ }
+
+ }
+
+ /**
+ * Clear all items in wait
+ */
+ clear(){
+ this._q = [];
+ }
+
+ /**
+ * Get the number of tasks in the queue
+ * @return {number} tasks
+ */
+ length(){
+ return this._q.length;
+ }
+
+ /**
+ * Pause a running queue
+ */
+ pause(){
+ this.paused = true;
+ }
+
+ /**
+ * End the queue
+ */
+ stop(){
+ this._q = [];
+ this.running = false;
+ this.paused = true;
+ }
+}
+
+
+/**
+ * Create a new task from a callback
+ * @class
+ * @private
+ * @param {function} task
+ * @param {array} args
+ * @param {scope} context
+ * @return {function} task
+ */
+class Task {
+ constructor(task, args, context){
+
+ return function(){
+ var toApply = arguments || [];
+
+ return new Promise( (resolve, reject) => {
+ var callback = function(value, err){
+ if (!value && err) {
+ reject(err);
+ } else {
+ resolve(value);
+ }
+ };
+ // Add the callback to the arguments list
+ toApply.push(callback);
+
+ // Apply all arguments to the functions
+ task.apply(context || this, toApply);
+
+ });
+
+ };
+
+ }
+}
+
+
+export default Queue;
+export { Task };
diff --git a/lib/epub.js/src/utils/replacements.js b/lib/epub.js/src/utils/replacements.js
new file mode 100644
index 0000000..a271088
--- /dev/null
+++ b/lib/epub.js/src/utils/replacements.js
@@ -0,0 +1,138 @@
+import { qs, qsa } from "./core";
+import Url from "./url";
+import Path from "./path";
+
+export function replaceBase(doc, section){
+ var base;
+ var head;
+ var url = section.url;
+ var absolute = (url.indexOf("://") > -1);
+
+ if(!doc){
+ return;
+ }
+
+ head = qs(doc, "head");
+ base = qs(head, "base");
+
+ if(!base) {
+ base = doc.createElement("base");
+ head.insertBefore(base, head.firstChild);
+ }
+
+ // Fix for Safari crashing if the url doesn't have an origin
+ if (!absolute && window && window.location) {
+ url = window.location.origin + url;
+ }
+
+ base.setAttribute("href", url);
+}
+
+export function replaceCanonical(doc, section){
+ var head;
+ var link;
+ var url = section.canonical;
+
+ if(!doc){
+ return;
+ }
+
+ head = qs(doc, "head");
+ link = qs(head, "link[rel='canonical']");
+
+ if (link) {
+ link.setAttribute("href", url);
+ } else {
+ link = doc.createElement("link");
+ link.setAttribute("rel", "canonical");
+ link.setAttribute("href", url);
+ head.appendChild(link);
+ }
+}
+
+export function replaceMeta(doc, section){
+ var head;
+ var meta;
+ var id = section.idref;
+ if(!doc){
+ return;
+ }
+
+ head = qs(doc, "head");
+ meta = qs(head, "link[property='dc.identifier']");
+
+ if (meta) {
+ meta.setAttribute("content", id);
+ } else {
+ meta = doc.createElement("meta");
+ meta.setAttribute("name", "dc.identifier");
+ meta.setAttribute("content", id);
+ head.appendChild(meta);
+ }
+}
+
+// TODO: move me to Contents
+export function replaceLinks(contents, fn) {
+
+ var links = contents.querySelectorAll("a[href]");
+
+ if (!links.length) {
+ return;
+ }
+
+ var base = qs(contents.ownerDocument, "base");
+ var location = base ? base.getAttribute("href") : undefined;
+ var replaceLink = function(link){
+ var href = link.getAttribute("href");
+
+ if(href.indexOf("mailto:") === 0){
+ return;
+ }
+
+ var absolute = (href.indexOf("://") > -1);
+
+ if(absolute){
+
+ link.setAttribute("target", "_blank");
+
+ }else{
+ var linkUrl;
+ try {
+ linkUrl = new Url(href, location);
+ } catch(error) {
+ // NOOP
+ }
+
+ link.onclick = function(){
+
+ if(linkUrl && linkUrl.hash) {
+ fn(linkUrl.Path.path + linkUrl.hash);
+ } else if(linkUrl){
+ fn(linkUrl.Path.path);
+ } else {
+ fn(href);
+ }
+
+ return false;
+ };
+ }
+ }.bind(this);
+
+ for (var i = 0; i < links.length; i++) {
+ replaceLink(links[i]);
+ }
+
+
+}
+
+export function substitute(content, urls, replacements) {
+ urls.forEach(function(url, i){
+ if (url && replacements[i]) {
+ // Account for special characters in the file name.
+ // See https://stackoverflow.com/a/6318729.
+ url = url.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ content = content.replace(new RegExp(url, "g"), replacements[i]);
+ }
+ });
+ return content;
+}
diff --git a/lib/epub.js/src/utils/request.js b/lib/epub.js/src/utils/request.js
new file mode 100644
index 0000000..0de3b86
--- /dev/null
+++ b/lib/epub.js/src/utils/request.js
@@ -0,0 +1,150 @@
+import {defer, isXml, parse} from "./core";
+import Path from "./path";
+
+function request(url, type, withCredentials, headers) {
+ var supportsURL = (typeof window != "undefined") ? window.URL : false; // TODO: fallback for url if window isn't defined
+ var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
+
+ var deferred = new defer();
+
+ var xhr = new XMLHttpRequest();
+
+ //-- Check from PDF.js:
+ // https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
+ var xhrPrototype = XMLHttpRequest.prototype;
+
+ var header;
+
+ if (!("overrideMimeType" in xhrPrototype)) {
+ // IE10 might have response, but not overrideMimeType
+ Object.defineProperty(xhrPrototype, "overrideMimeType", {
+ value: function xmlHttpRequestOverrideMimeType() {}
+ });
+ }
+
+ if(withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ xhr.onreadystatechange = handler;
+ xhr.onerror = err;
+
+ xhr.open("GET", url, true);
+
+ for(header in headers) {
+ xhr.setRequestHeader(header, headers[header]);
+ }
+
+ if(type == "json") {
+ xhr.setRequestHeader("Accept", "application/json");
+ }
+
+ // If type isn"t set, determine it from the file extension
+ if(!type) {
+ type = new Path(url).extension;
+ }
+
+ if(type == "blob"){
+ xhr.responseType = BLOB_RESPONSE;
+ }
+
+
+ if(isXml(type)) {
+ // xhr.responseType = "document";
+ xhr.overrideMimeType("text/xml"); // for OPF parsing
+ }
+
+ if(type == "xhtml") {
+ // xhr.responseType = "document";
+ }
+
+ if(type == "html" || type == "htm") {
+ // xhr.responseType = "document";
+ }
+
+ if(type == "binary") {
+ xhr.responseType = "arraybuffer";
+ }
+
+ xhr.send();
+
+ function err(e) {
+ deferred.reject(e);
+ }
+
+ function handler() {
+ if (this.readyState === XMLHttpRequest.DONE) {
+ var responseXML = false;
+
+ if(this.responseType === "" || this.responseType === "document") {
+ responseXML = this.responseXML;
+ }
+
+ if (this.status === 200 || this.status === 0 || responseXML) { //-- Firefox is reporting 0 for blob urls
+ var r;
+
+ if (!this.response && !responseXML) {
+ deferred.reject({
+ status: this.status,
+ message : "Empty Response",
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+
+ if (this.status === 403) {
+ deferred.reject({
+ status: this.status,
+ response: this.response,
+ message : "Forbidden",
+ stack : new Error().stack
+ });
+ return deferred.promise;
+ }
+ if(responseXML){
+ r = this.responseXML;
+ } else
+ if(isXml(type)){
+ // xhr.overrideMimeType("text/xml"); // for OPF parsing
+ // If this.responseXML wasn't set, try to parse using a DOMParser from text
+ r = parse(this.response, "text/xml");
+ }else
+ if(type == "xhtml"){
+ r = parse(this.response, "application/xhtml+xml");
+ }else
+ if(type == "html" || type == "htm"){
+ r = parse(this.response, "text/html");
+ }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({
+ status: this.status,
+ message : this.response,
+ stack : new Error().stack
+ });
+
+ }
+ }
+ }
+
+ return deferred.promise;
+}
+
+export default request;
diff --git a/lib/epub.js/src/utils/scrolltype.js b/lib/epub.js/src/utils/scrolltype.js
new file mode 100644
index 0000000..7d2e47b
--- /dev/null
+++ b/lib/epub.js/src/utils/scrolltype.js
@@ -0,0 +1,55 @@
+// Detect RTL scroll type
+// Based on https://github.com/othree/jquery.rtl-scroll-type/blob/master/src/jquery.rtl-scroll.js
+export default function scrollType() {
+ var type = "reverse";
+ var definer = createDefiner();
+ document.body.appendChild(definer);
+
+ if (definer.scrollLeft > 0) {
+ type = "default";
+ } else {
+ if (typeof Element !== 'undefined' && Element.prototype.scrollIntoView) {
+ definer.children[0].children[1].scrollIntoView();
+ if (definer.scrollLeft < 0) {
+ type = "negative";
+ }
+ } else {
+ definer.scrollLeft = 1;
+ if (definer.scrollLeft === 0) {
+ type = "negative";
+ }
+ }
+ }
+
+ document.body.removeChild(definer);
+ return type;
+}
+
+export function createDefiner() {
+ var definer = document.createElement('div');
+ definer.dir="rtl";
+
+ definer.style.position = "fixed";
+ definer.style.width = "1px";
+ definer.style.height = "1px";
+ definer.style.top = "0px";
+ definer.style.left = "0px";
+ definer.style.overflow = "hidden";
+
+ var innerDiv = document.createElement('div');
+ innerDiv.style.width = "2px";
+
+ var spanA = document.createElement('span');
+ spanA.style.width = "1px";
+ spanA.style.display = "inline-block";
+
+ var spanB = document.createElement('span');
+ spanB.style.width = "1px";
+ spanB.style.display = "inline-block";
+
+ innerDiv.appendChild(spanA);
+ innerDiv.appendChild(spanB);
+ definer.appendChild(innerDiv);
+
+ return definer;
+}
diff --git a/lib/epub.js/src/utils/url.js b/lib/epub.js/src/utils/url.js
new file mode 100644
index 0000000..3cc8c04
--- /dev/null
+++ b/lib/epub.js/src/utils/url.js
@@ -0,0 +1,108 @@
+import Path from "./path";
+import path from "path-webpack";
+
+/**
+ * creates a Url object for parsing and manipulation of a url string
+ * @param {string} urlString a url string (relative or absolute)
+ * @param {string} [baseString] optional base for the url,
+ * default to window.location.href
+ */
+class Url {
+ constructor(urlString, baseString) {
+ var absolute = (urlString.indexOf("://") > -1);
+ var pathname = urlString;
+ var basePath;
+
+ this.Url = undefined;
+ this.href = urlString;
+ this.protocol = "";
+ this.origin = "";
+ this.hash = "";
+ this.hash = "";
+ this.search = "";
+ this.base = baseString;
+
+ if (!absolute &&
+ baseString !== false &&
+ typeof(baseString) !== "string" &&
+ window && window.location) {
+ this.base = window.location.href;
+ }
+
+ // URL Polyfill doesn't throw an error if base is empty
+ if (absolute || this.base) {
+ try {
+ if (this.base) { // Safari doesn't like an undefined base
+ this.Url = new URL(urlString, this.base);
+ } else {
+ this.Url = new URL(urlString);
+ }
+ this.href = this.Url.href;
+
+ this.protocol = this.Url.protocol;
+ this.origin = this.Url.origin;
+ this.hash = this.Url.hash;
+ this.search = this.Url.search;
+
+ pathname = this.Url.pathname + (this.Url.search ? this.Url.search : '');
+ } catch (e) {
+ // Skip URL parsing
+ this.Url = undefined;
+ // resolve the pathname from the base
+ if (this.base) {
+ basePath = new Path(this.base);
+ pathname = basePath.resolve(pathname);
+ }
+ }
+ }
+
+ this.Path = new Path(pathname);
+
+ this.directory = this.Path.directory;
+ this.filename = this.Path.filename;
+ this.extension = this.Path.extension;
+
+ }
+
+ /**
+ * @returns {Path}
+ */
+ path () {
+ return this.Path;
+ }
+
+ /**
+ * Resolves a relative path to a absolute url
+ * @param {string} what
+ * @returns {string} url
+ */
+ resolve (what) {
+ var isAbsolute = (what.indexOf("://") > -1);
+ var fullpath;
+
+ if (isAbsolute) {
+ return what;
+ }
+
+ fullpath = path.resolve(this.directory, what);
+ return this.origin + fullpath;
+ }
+
+ /**
+ * Resolve a path relative to the url
+ * @param {string} what
+ * @returns {string} path
+ */
+ relative (what) {
+ return path.relative(what, this.directory);
+ }
+
+ /**
+ * @returns {string}
+ */
+ toString () {
+ return this.href;
+ }
+}
+
+export default Url;