1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-03 14:59:18 +02:00

Intial move to ES2015

This commit is contained in:
Fred Chasen 2016-12-06 15:04:16 +01:00
parent b0944bdff8
commit 353dfa62fd
46 changed files with 16839 additions and 18742 deletions

15
.babelrc Normal file
View file

@ -0,0 +1,15 @@
{
"presets": [
["env", {
"targets": {
"chrome": 54,
"safari" : 10,
"firefox" : 50,
"edge" : 14
}
}]
],
"plugins": [
"add-module-exports"
]
}

3
.gitignore vendored
View file

@ -5,3 +5,6 @@ components
node_modules
bower_components
books
lib
dist
documentation/html

View file

@ -26,6 +26,10 @@
"examples"
],
"dependencies": {
"es6-promise": "~4.0.5"
"es6-promise": "~4.0.5",
"jszip": "^3.1.1",
"path-webpack": "^0.0.2",
"stream-browserify": "^2.0.1",
"xmldom": "^0.1.22"
}
}

17946
dist/epub.js vendored

File diff suppressed because it is too large Load diff

2
dist/epub.js.map vendored

File diff suppressed because one or more lines are too long

5
dist/epub.min.js vendored

File diff suppressed because one or more lines are too long

2110
dist/polyfills.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -24,7 +24,7 @@ module.exports = function(config) {
{pattern: 'node_modules/jszip/dist/jszip.js', watched: false, included: true, served: true},
{pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', watched: false, included: true, served: true},
// {pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', watched: false, included: true, served: true},
{pattern: 'libs/url/url.js', watched: false, included: true, served: true}
@ -53,6 +53,22 @@ module.exports = function(config) {
alias: {
path: "path-webpack"
}
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ['es2015'],
plugins: [
"add-module-exports",
"transform-runtime"
]
}
}
]
}
},

View file

@ -1,20 +1,34 @@
{
"name": "epubjs",
"version": "0.3.0",
"description": "Render Epubs",
"main": "src/epub.js",
"version": "0.3.1",
"description": "Parse and Render Epubs",
"main": "lib/index.js",
"jsnext:main" : "src/index.js",
"repository": "https://github.com/futurepress/epub.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
"start": "webpack-dev-server --inline",
"build": "./node_modules/.bin/gulp minify"
"documentation": "./node_modules/.bin/gulp docs",
"start": "webpack-dev-server --inline --d",
"build": "webpack --progress",
"minify": "NODE_ENV=production webpack --progress",
"legacy": "NODE_ENV=production LEGACY=true webpack --progress",
"compile": "babel --optional runtime -d lib/ src/",
"prepublish": "npm run compile && npm run build && npm run minify && npm run legacy"
},
"author": "fchasen@gmail.com",
"license": "BSD-2-Clause",
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.16.0",
"babel-preset-env": "0.0.9",
"babili-webpack-plugin": "0.0.7",
"colors": "^1.1.2",
"connect": "^3.0.1",
"express": "^4.5.1",
@ -52,11 +66,10 @@
"webpack-dev-server": "^v2.1.0-beta.10"
},
"dependencies": {
"es6-promise": "^4.0.5",
"event-emitter": "^0.3.4",
"jszip": "^3.1.1",
"xmldom": "^0.1.22",
"path-webpack": "^0.0.2",
"stream-browserify": "^2.0.1"
"stream-browserify": "^2.0.1",
"xmldom": "^0.1.22"
}
}

View file

@ -1,245 +1,247 @@
var core = require('./core');
var request = require('./request');
var mime = require('../libs/mime/mime');
var Path = require('./core').Path;
import {defer, isXml, parse} from './utils/core';
import request from './request';
import mime from '../libs/mime/mime';
import Path from './utils/path';
/**
* Handles Unzipping a requesting files from an Epub Archive
* @class
*/
function Archive() {
this.zip = undefined;
this.checkRequirements();
this.urlCache = {};
class Archive {
constructor() {
this.zip = undefined;
this.checkRequirements();
this.urlCache = {};
}
/**
* Checks to see if JSZip exists in global namspace,
* Requires JSZip if it isn't there
* @private
*/
checkRequirements(){
try {
if (typeof JSZip === 'undefined') {
JSZip = require('jszip');
}
this.zip = new JSZip();
} catch (e) {
console.error("JSZip lib not loaded");
}
};
/**
* Open an archive
* @param {binary} input
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
open(input, isBase64){
return this.zip.loadAsync(input, {"base64": isBase64});
};
/**
* Load and Open an archive
* @param {string} zipUrl
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
openUrl(zipUrl, isBase64){
return request(zipUrl, "binary")
.then(function(data){
return this.zip.loadAsync(data, {"base64": isBase64});
}.bind(this));
};
/**
* Request
* @param {string} url a url to request from the archive
* @param {[string]} type specify the type of the returned result
* @return {Promise}
*/
request(url, type){
var deferred = new defer();
var response;
var r;
var path = new Path(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = path.extension;
}
if(type == 'blob'){
response = this.getBlob(url);
} else {
response = this.getText(url);
}
if (response) {
response.then(function (r) {
let result = this.handleResponse(r, type);
deferred.resolve(result);
}.bind(this));
} else {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Handle the response from request
* @private
* @param {any} response
* @param {[string]} type
* @return {any} the parsed result
*/
handleResponse(response, type){
var r;
if(type == "json") {
r = JSON.parse(response);
}
else
if(isXml(type)) {
r = parse(response, "text/xml");
}
else
if(type == 'xhtml') {
r = parse(response, "application/xhtml+xml");
}
else
if(type == 'html' || type == 'htm') {
r = parse(response, "text/html");
} else {
r = response;
}
return r;
};
/**
* Get a Blob from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {Blob}
*/
getBlob(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("uint8array").then(function(uint8array) {
return new Blob([uint8array], {type : mimeType});
});
}
};
/**
* Get Text from Archive by Url
* @param {string} url
* @param {[string]} encoding
* @return {string}
*/
getText(url, encoding){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
return entry.async("string").then(function(text) {
return text;
});
}
};
/**
* Get a base64 encoded result from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {string} base64 encoded
*/
getBase64(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("base64").then(function(data) {
return "data:" + mimeType + ";base64," + data;
});
}
};
/**
* Create a Url from an unarchived item
* @param {string} url
* @param {[object]} options.base64 use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
createUrl(url, options){
var deferred = new defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var blob;
var response;
var useBase64 = options && options.base64;
if(url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
if (useBase64) {
response = this.getBase64(url);
if (response) {
response.then(function(tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
} else {
response = this.getBlob(url);
if (response) {
response.then(function(blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
}
if (!response) {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Revoke Temp Url for a achive item
* @param {string} url url of the item in the archive
*/
revokeUrl(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
};
}
/**
* Checks to see if JSZip exists in global namspace,
* Requires JSZip if it isn't there
* @private
*/
Archive.prototype.checkRequirements = function(){
try {
if (typeof JSZip === 'undefined') {
JSZip = require('jszip');
}
this.zip = new JSZip();
} catch (e) {
console.error("JSZip lib not loaded");
}
};
/**
* Open an archive
* @param {binary} input
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
Archive.prototype.open = function(input, isBase64){
return this.zip.loadAsync(input, {"base64": isBase64});
};
/**
* Load and Open an archive
* @param {string} zipUrl
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
Archive.prototype.openUrl = function(zipUrl, isBase64){
return request(zipUrl, "binary")
.then(function(data){
return this.zip.loadAsync(data, {"base64": isBase64});
}.bind(this));
};
/**
* Request
* @param {string} url a url to request from the archive
* @param {[string]} type specify the type of the returned result
* @return {Promise}
*/
Archive.prototype.request = function(url, type){
var deferred = new core.defer();
var response;
var r;
var path = new Path(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = path.extension;
}
if(type == 'blob'){
response = this.getBlob(url);
} else {
response = this.getText(url);
}
if (response) {
response.then(function (r) {
result = this.handleResponse(r, type);
deferred.resolve(result);
}.bind(this));
} else {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Handle the response from request
* @private
* @param {any} response
* @param {[string]} type
* @return {any} the parsed result
*/
Archive.prototype.handleResponse = function(response, type){
var r;
if(type == "json") {
r = JSON.parse(response);
}
else
if(core.isXml(type)) {
r = core.parse(response, "text/xml");
}
else
if(type == 'xhtml') {
r = core.parse(response, "application/xhtml+xml");
}
else
if(type == 'html' || type == 'htm') {
r = core.parse(response, "text/html");
} else {
r = response;
}
return r;
};
/**
* Get a Blob from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {Blob}
*/
Archive.prototype.getBlob = function(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("uint8array").then(function(uint8array) {
return new Blob([uint8array], {type : mimeType});
});
}
};
/**
* Get Text from Archive by Url
* @param {string} url
* @param {[string]} encoding
* @return {string}
*/
Archive.prototype.getText = function(url, encoding){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
return entry.async("string").then(function(text) {
return text;
});
}
};
/**
* Get a base64 encoded result from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {string} base64 encoded
*/
Archive.prototype.getBase64 = function(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("base64").then(function(data) {
return "data:" + mimeType + ";base64," + data;
});
}
};
/**
* Create a Url from an unarchived item
* @param {string} url
* @param {[object]} options.base64 use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
Archive.prototype.createUrl = function(url, options){
var deferred = new core.defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var blob;
var response;
var useBase64 = options && options.base64;
if(url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
if (useBase64) {
response = this.getBase64(url);
if (response) {
response.then(function(tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
} else {
response = this.getBlob(url);
if (response) {
response.then(function(blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
}
if (!response) {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Revoke Temp Url for a achive item
* @param {string} url url of the item in the archive
*/
Archive.prototype.revokeUrl = function(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
};
module.exports = Archive;
export default Archive;

View file

@ -1,22 +1,21 @@
var EventEmitter = require('event-emitter');
var path = require('path');
var core = require('./core');
var Url = require('./core').Url;
var Path = require('./core').Path;
var Spine = require('./spine');
var Locations = require('./locations');
var Container = require('./container');
var Packaging = require('./packaging');
var Navigation = require('./navigation');
var Resources = require('./resources');
var PageList = require('./pagelist');
var Rendition = require('./rendition');
var Archive = require('./archive');
var request = require('./request');
var EpubCFI = require('./epubcfi');
import EventEmitter from 'event-emitter';
// import path from 'path';
import {extend, defer} from './utils/core';
import Url from './utils/url';
import Path from './utils/path';
import Spine from './spine';
import Locations from './locations';
import Container from './container';
import Packaging from './packaging';
import Navigation from './navigation';
import Resources from './resources';
import PageList from './pagelist';
import Rendition from './rendition';
import Archive from './archive';
import request from './request';
import EpubCFI from './epubcfi';
// Const
var CONTAINER_PATH = "META-INF/container.xml";
const CONTAINER_PATH = "META-INF/container.xml";
/**
* Creates a new Book
@ -32,459 +31,461 @@ var CONTAINER_PATH = "META-INF/container.xml";
* @example new Book("/path/to/book.epub", {})
* @example new Book({ replacements: "blobUrl" })
*/
function Book(url, options){
class Book {
constructor(url, options) {
// Allow passing just options to the Book
if (typeof(options) === "undefined"
&& typeof(url) === "object") {
options = url;
url = undefined;
}
// Allow passing just options to the Book
if (typeof(options) === "undefined"
&& typeof(url) === "object") {
options = url;
url = undefined;
}
this.settings = extend(this.settings || {}, {
requestMethod: undefined,
requestCredentials: undefined,
requestHeaders: undefined,
encoding: undefined,
replacements: 'base64'
});
this.settings = core.extend(this.settings || {}, {
requestMethod: undefined,
requestCredentials: undefined,
requestHeaders: undefined,
encoding: undefined,
replacements: 'base64'
});
core.extend(this.settings, options);
extend(this.settings, options);
// Promises
this.opening = new core.defer();
/**
* @property {promise} opened returns after the book is loaded
*/
this.opened = this.opening.promise;
this.isOpen = false;
// Promises
this.opening = new defer();
/**
* @property {promise} opened returns after the book is loaded
*/
this.opened = this.opening.promise;
this.isOpen = false;
this.loading = {
manifest: new core.defer(),
spine: new core.defer(),
metadata: new core.defer(),
cover: new core.defer(),
navigation: new core.defer(),
pageList: new core.defer(),
resources: new core.defer()
this.loading = {
manifest: new defer(),
spine: new defer(),
metadata: new defer(),
cover: new defer(),
navigation: new defer(),
pageList: new defer(),
resources: new defer()
};
this.loaded = {
manifest: this.loading.manifest.promise,
spine: this.loading.spine.promise,
metadata: this.loading.metadata.promise,
cover: this.loading.cover.promise,
navigation: this.loading.navigation.promise,
pageList: this.loading.pageList.promise,
resources: this.loading.resources.promise
};
// this.ready = RSVP.hash(this.loaded);
/**
* @property {promise} ready returns after the book is loaded and parsed
* @private
*/
this.ready = Promise.all([this.loaded.manifest,
this.loaded.spine,
this.loaded.metadata,
this.loaded.cover,
this.loaded.navigation,
this.loaded.resources ]);
// Queue for methods used before opening
this.isRendered = false;
// this._q = queue(this);
/**
* @property {method} request
* @private
*/
this.request = this.settings.requestMethod || request;
/**
* @property {Spine} spine
*/
this.spine = new Spine();
/**
* @property {Locations} locations
*/
this.locations = new Locations(this.spine, this.load.bind(this));
/**
* @property {Navigation} navigation
*/
this.navigation = undefined;
/**
* @property {PageList} pagelist
*/
this.pageList = new PageList();
/**
* @property {Url} url
* @private
*/
this.url = undefined;
/**
* @property {Path} path
* @private
*/
this.path = undefined;
/**
* @property {boolean} archived
* @private
*/
this.archived = false;
if(url) {
this.open(url).catch((error) => {
var err = new Error("Cannot load book at "+ url );
console.error(err);
this.emit("openFailed", err);
console.log(error);
});
}
};
this.loaded = {
manifest: this.loading.manifest.promise,
spine: this.loading.spine.promise,
metadata: this.loading.metadata.promise,
cover: this.loading.cover.promise,
navigation: this.loading.navigation.promise,
pageList: this.loading.pageList.promise,
resources: this.loading.resources.promise
/**
* Open a epub or url
* @param {string} input URL, Path or ArrayBuffer
* @param {string} [what] to force opening
* @returns {Promise} of when the book has been loaded
* @example book.open("/path/to/book.epub")
*/
open(input, what) {
var opening;
var type = what || this.determineType(input);
if (type === "binary") {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "epub") {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, 'binary')
.then(this.openEpub.bind(this));
} else if(type == "opf") {
this.url = new Url(input);
opening = this.openPackaging(this.url.Path.toString());
} else {
this.url = new Url(input);
opening = this.openContainer(CONTAINER_PATH)
.then(this.openPackaging.bind(this));
}
return opening;
}
/**
* Open an archived epub
* @private
* @param {binary} data
* @param {[string]} encoding
* @return {Promise}
*/
openEpub(data, encoding) {
return this.unarchive(data, encoding || this.settings.encoding)
.then(() => {
return this.openContainer(CONTAINER_PATH);
})
.then((packagePath) => {
return this.openPackaging(packagePath);
});
}
/**
* Open the epub container
* @private
* @param {string} url
* @return {string} packagePath
*/
openContainer(url) {
return this.load(url)
.then((xml) => {
this.container = new Container(xml);
return this.resolve(this.container.packagePath);
});
}
/**
* Open the Open Packaging Format Xml
* @private
* @param {string} url
* @return {Promise}
*/
openPackaging(url) {
var packageUrl;
this.path = new Path(url);
return this.load(url)
.then((xml) => {
this.packaging = new Packaging(xml);
return this.unpack(this.packaging);
});
}
/**
* Load a resource from the Book
* @param {string} path path to the resource to load
* @return {Promise} returns a promise with the requested resource
*/
load(path) {
var resolved;
if(this.archived) {
resolved = this.resolve(path);
return this.archive.request(resolved);
} else {
resolved = this.resolve(path);
return this.request(resolved, null, this.settings.requestCredentials, this.settings.requestHeaders);
}
}
/**
* Resolve a path to it's absolute position in the Book
* @param {string} path
* @param {[boolean]} absolute force resolving the full URL
* @return {string} the resolved path string
*/
resolve(path, absolute) {
var resolved = path;
var isAbsolute = (path.indexOf('://') > -1);
if (isAbsolute) {
return path;
}
if (this.path) {
resolved = this.path.resolve(path);
}
if(absolute != false && this.url) {
resolved = this.url.resolve(resolved);
}
return resolved;
}
/**
* Determine the type of they input passed to open
* @private
* @param {string} input
* @return {string} binary | directory | epub | opf
*/
determineType(input) {
var url;
var path;
var extension;
if (typeof(input) != "string") {
return "binary";
}
url = new Url(input);
path = url.path();
extension = path.extension;
if (!extension) {
return "directory";
}
if(extension === "epub"){
return "epub";
}
if(extension === "opf"){
return "opf";
}
};
// this.ready = RSVP.hash(this.loaded);
/**
* @property {promise} ready returns after the book is loaded and parsed
* unpack the contents of the Books packageXml
* @private
* @param {document} packageXml XML Document
*/
this.ready = Promise.all([this.loaded.manifest,
this.loaded.spine,
this.loaded.metadata,
this.loaded.cover,
this.loaded.navigation,
this.loaded.resources ]);
unpack(opf) {
this.package = opf;
this.spine.unpack(this.package, this.resolve.bind(this));
this.resources = new Resources(this.package.manifest, {
archive: this.archive,
resolver: this.resolve.bind(this),
replacements: this.settings.replacements
});
this.loadNavigation(this.package).then(() => {
this.toc = this.navigation.toc;
this.loading.navigation.resolve(this.navigation);
});
this.cover = this.resolve(this.package.coverPath);
// Resolve promises
this.loading.manifest.resolve(this.package.manifest);
this.loading.metadata.resolve(this.package.metadata);
this.loading.spine.resolve(this.spine);
this.loading.cover.resolve(this.cover);
this.loading.resources.resolve(this.resources);
this.loading.pageList.resolve(this.pageList);
// Queue for methods used before opening
this.isRendered = false;
// this._q = core.queue(this);
this.isOpen = true;
/**
* @property {method} request
* @private
*/
this.request = this.settings.requestMethod || request;
/**
* @property {Spine} spine
*/
this.spine = new Spine();
/**
* @property {Locations} locations
*/
this.locations = new Locations(this.spine, this.load.bind(this));
/**
* @property {Navigation} navigation
*/
this.navigation = undefined;
/**
* @property {PageList} pagelist
*/
this.pageList = new PageList();
/**
* @property {Url} url
* @private
*/
this.url = undefined;
/**
* @property {Path} path
* @private
*/
this.path = undefined;
/**
* @property {boolean} archived
* @private
*/
this.archived = false;
if(url) {
this.open(url).catch(function (error) {
var err = new Error("Cannot load book at "+ url );
console.error(err);
this.emit("openFailed", err);
console.log(error);
}.bind(this));
}
};
/**
* Open a epub or url
* @param {string} input URL, Path or ArrayBuffer
* @param {string} [what] to force opening
* @returns {Promise} of when the book has been loaded
* @example book.open("/path/to/book.epub")
*/
Book.prototype.open = function(input, what){
var opening;
var type = what || this.determineType(input);
if (type === "binary") {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "epub") {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, 'binary')
.then(this.openEpub.bind(this));
} else if(type == "opf") {
this.url = new Url(input);
opening = this.openPackaging(this.url.Path.toString());
} else {
this.url = new Url(input);
opening = this.openContainer(CONTAINER_PATH)
.then(this.openPackaging.bind(this));
}
return opening;
};
/**
* Open an archived epub
* @private
* @param {binary} data
* @param {[string]} encoding
* @return {Promise}
*/
Book.prototype.openEpub = function(data, encoding){
return this.unarchive(data, encoding || this.settings.encoding)
.then(function() {
return this.openContainer(CONTAINER_PATH);
}.bind(this))
.then(function(packagePath) {
return this.openPackaging(packagePath);
}.bind(this));
};
/**
* Open the epub container
* @private
* @param {string} url
* @return {string} packagePath
*/
Book.prototype.openContainer = function(url){
return this.load(url)
.then(function(xml) {
this.container = new Container(xml);
return this.resolve(this.container.packagePath);
}.bind(this));
};
/**
* Open the Open Packaging Format Xml
* @private
* @param {string} url
* @return {Promise}
*/
Book.prototype.openPackaging = function(url){
var packageUrl;
this.path = new Path(url);
return this.load(url)
.then(function(xml) {
this.packaging = new Packaging(xml);
return this.unpack(this.packaging);
}.bind(this));
};
/**
* Load a resource from the Book
* @param {string} path path to the resource to load
* @return {Promise} returns a promise with the requested resource
*/
Book.prototype.load = function (path) {
var resolved;
if(this.archived) {
resolved = this.resolve(path);
return this.archive.request(resolved);
} else {
resolved = this.resolve(path);
return this.request(resolved, null, this.settings.requestCredentials, this.settings.requestHeaders);
}
};
/**
* Resolve a path to it's absolute position in the Book
* @param {string} path
* @param {[boolean]} absolute force resolving the full URL
* @return {string} the resolved path string
*/
Book.prototype.resolve = function (path, absolute) {
var resolved = path;
var isAbsolute = (path.indexOf('://') > -1);
if (isAbsolute) {
return path;
}
if (this.path) {
resolved = this.path.resolve(path);
}
if(absolute != false && this.url) {
resolved = this.url.resolve(resolved);
}
return resolved;
}
/**
* Determine the type of they input passed to open
* @private
* @param {string} input
* @return {string} binary | directory | epub | opf
*/
Book.prototype.determineType = function(input) {
var url;
var path;
var extension;
if (typeof(input) != "string") {
return "binary";
}
url = new Url(input);
path = url.path();
extension = path.extension;
if (!extension) {
return "directory";
}
if(extension === "epub"){
return "epub";
}
if(extension === "opf"){
return "opf";
}
};
/**
* unpack the contents of the Books packageXml
* @private
* @param {document} packageXml XML Document
*/
Book.prototype.unpack = function(opf){
this.package = opf;
this.spine.unpack(this.package, this.resolve.bind(this));
this.resources = new Resources(this.package.manifest, {
archive: this.archive,
resolver: this.resolve.bind(this),
replacements: this.settings.replacements
});
this.loadNavigation(this.package).then(function(){
this.toc = this.navigation.toc;
this.loading.navigation.resolve(this.navigation);
}.bind(this));
this.cover = this.resolve(this.package.coverPath);
// Resolve promises
this.loading.manifest.resolve(this.package.manifest);
this.loading.metadata.resolve(this.package.metadata);
this.loading.spine.resolve(this.spine);
this.loading.cover.resolve(this.cover);
this.loading.resources.resolve(this.resources);
this.loading.pageList.resolve(this.pageList);
this.isOpen = true;
if(this.archived) {
this.replacements().then(function() {
if(this.archived) {
this.replacements().then(() => {
this.opening.resolve(this);
});
} else {
// Resolve book opened promise
this.opening.resolve(this);
}.bind(this));
} else {
// Resolve book opened promise
this.opening.resolve(this);
}
}
};
/**
* Load Navigation and PageList from package
* @private
* @param {document} opf XML Document
*/
loadNavigation(opf) {
var navPath = opf.navPath || opf.ncxPath;
/**
* Load Navigation and PageList from package
* @private
* @param {document} opf XML Document
*/
Book.prototype.loadNavigation = function(opf){
var navPath = opf.navPath || opf.ncxPath;
if (!navPath) {
return;
}
if (!navPath) {
return;
return this.load(navPath, 'xml')
.then((xml) => {
this.navigation = new Navigation(xml);
this.pageList = new PageList(xml);
return this.navigation;
});
}
return this.load(navPath, 'xml')
.then(function(xml) {
this.navigation = new Navigation(xml);
this.pageList = new PageList(xml);
return this.navigation;
}.bind(this));
};
/**
* Alias for book.spine.get
* @param {string} target
*/
section(target) {
return this.spine.get(target);
}
/**
* Alias for book.spine.get
* @param {string} target
*/
Book.prototype.section = function(target) {
return this.spine.get(target);
};
/**
* Sugar to render a book
* @param {element} element element to add the views to
* @param {[object]} options
* @return {Rendition}
*/
renderTo(element, options) {
// var renderMethod = (options && options.method) ?
// options.method :
// "single";
/**
* Sugar to render a book
* @param {element} element element to add the views to
* @param {[object]} options
* @return {Rendition}
*/
Book.prototype.renderTo = function(element, options) {
// var renderMethod = (options && options.method) ?
// options.method :
// "single";
this.rendition = new Rendition(this, options);
this.rendition.attachTo(element);
this.rendition = new Rendition(this, options);
this.rendition.attachTo(element);
return this.rendition;
};
return this.rendition;
};
/**
* Set if request should use withCredentials
* @param {boolean} credentials
*/
setRequestCredentials(credentials) {
this.settings.requestCredentials = credentials;
};
/**
* Set if request should use withCredentials
* @param {boolean} credentials
*/
Book.prototype.setRequestCredentials = function(credentials) {
this.settings.requestCredentials = credentials;
};
/**
* Set headers request should use
* @param {object} headers
*/
setRequestHeaders(headers) {
this.settings.requestHeaders = headers;
};
/**
* Set headers request should use
* @param {object} headers
*/
Book.prototype.setRequestHeaders = function(headers) {
this.settings.requestHeaders = headers;
};
/**
* Unarchive a zipped epub
* @private
* @param {binary} input epub data
* @param {[string]} encoding
* @return {Archive}
*/
unarchive(input, encoding) {
this.archive = new Archive();
return this.archive.open(input, encoding);
}
/**
* Unarchive a zipped epub
* @private
* @param {binary} input epub data
* @param {[string]} encoding
* @return {Archive}
*/
Book.prototype.unarchive = function(input, encoding){
this.archive = new Archive();
return this.archive.open(input, encoding);
};
/**
* Get the cover url
* @return {string} coverUrl
*/
Book.prototype.coverUrl = function(){
var retrieved = this.loaded.cover.
then(function(url) {
if(this.archived) {
// return this.archive.createUrl(this.cover);
return this.resources.get(this.cover);
}else{
return this.cover;
}
}.bind(this));
/**
* Get the cover url
* @return {string} coverUrl
*/
coverUrl() {
var retrieved = this.loaded.cover.
then((url) => {
if(this.archived) {
// return this.archive.createUrl(this.cover);
return this.resources.get(this.cover);
}else{
return this.cover;
}
});
return retrieved;
};
return retrieved;
}
/**
* load replacement urls
* @private
* @return {Promise} completed loading urls
*/
Book.prototype.replacements = function(){
this.spine.hooks.serialize.register(function(output, section) {
section.output = this.resources.substitute(output, section.url);
}.bind(this));
/**
* load replacement urls
* @private
* @return {Promise} completed loading urls
*/
replacements() {
this.spine.hooks.serialize.register((output, section) => {
section.output = this.resources.substitute(output, section.url);
});
return this.resources.replacements().
then(function() {
return this.resources.replaceCss();
}.bind(this));
};
return this.resources.replacements().
then(() => {
return this.resources.replaceCss();
});
}
/**
* Find a DOM Range for a given CFI Range
* @param {EpubCFI} cfiRange a epub cfi range
* @return {Range}
*/
Book.prototype.range = function(cfiRange) {
var cfi = new EpubCFI(cfiRange);
var item = this.spine.get(cfi.spinePos);
/**
* Find a DOM Range for a given CFI Range
* @param {EpubCFI} cfiRange a epub cfi range
* @return {Range}
*/
range(cfiRange) {
var cfi = new EpubCFI(cfiRange);
var item = this.spine.get(cfi.spinePos);
return item.load().then(function (contents) {
var range = cfi.toRange(item.document);
return range;
})
};
return item.load().then(function (contents) {
var range = cfi.toRange(item.document);
return range;
})
}
/**
* Generates the Book Key using the identifer in the manifest or other string provided
* @param {[string]} identifier to use instead of metadata identifier
* @return {string} key
*/
Book.prototype.key = function(identifier){
var ident = identifier || this.package.metadata.identifier || this.url.filename;
return "epubjs:" + (EPUBJS_VERSION || ePub.VERSION) + ":" + ident;
};
/**
* Generates the Book Key using the identifer in the manifest or other string provided
* @param {[string]} identifier to use instead of metadata identifier
* @return {string} key
*/
key(identifier) {
var ident = identifier || this.package.metadata.identifier || this.url.filename;
return "epubjs:" + (EPUBJS_VERSION || ePub.VERSION) + ":" + ident;
}
}
//-- Enable binding events to book
EventEmitter(Book.prototype);
module.exports = Book;
export default Book;

View file

@ -1,41 +1,43 @@
var path = require('path');
var core = require('./core');
var EpubCFI = require('./epubcfi');
import path from 'path-webpack';
import {qs} from './utils/core';
import EpubCFI from './epubcfi';
/**
* Handles Parsing and Accessing an Epub Container
* @class
* @param {[document]} containerDocument xml document
*/
function Container(containerDocument) {
if (containerDocument) {
this.parse(containerDocument);
}
};
/**
* Parse the Container XML
* @param {document} containerDocument
*/
Container.prototype.parse = function(containerDocument){
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile, fullpath, folder, encoding;
if(!containerDocument) {
console.error("Container File Not Found");
return;
class Container {
constructor(containerDocument) {
if (containerDocument) {
this.parse(containerDocument);
}
};
rootfile = core.qs(containerDocument, "rootfile");
/**
* Parse the Container XML
* @param {document} containerDocument
*/
parse(containerDocument){
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile, fullpath, folder, encoding;
if(!rootfile) {
console.error("No RootFile Found");
return;
}
if(!containerDocument) {
console.error("Container File Not Found");
return;
}
this.packagePath = rootfile.getAttribute('full-path');
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
};
rootfile = qs(containerDocument, "rootfile");
module.exports = Container;
if(!rootfile) {
console.error("No RootFile Found");
return;
}
this.packagePath = rootfile.getAttribute('full-path');
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
};
}
export default Container;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
var Book = require('./book');
var EpubCFI = require('./epubcfi');
var Rendition = require('./rendition');
var Contents = require('./contents');
import Book from './book';
import EpubCFI from './epubcfi';
import Rendition from './rendition';
import Contents from './contents';
/**
* Creates a new Book
@ -51,4 +51,4 @@ ePub.register.view("iframe", require('./managers/views/iframe'));
ePub.register.manager("default", require('./managers/default'));
ePub.register.manager("continuous", require('./managers/continuous'));
module.exports = ePub;
export default ePub;

File diff suppressed because it is too large Load diff

View file

@ -5,58 +5,59 @@
* @param {any} context scope of this
* @example this.content = new EPUBJS.Hook(this);
*/
function Hook(context){
this.context = context || this;
this.hooks = [];
};
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(){...});
*/
Hook.prototype.register = function(){
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]);
/**
* 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]);
}
}
}
}
};
};
/**
* Triggers a hook to run all functions
* @example this.content.trigger(args).then(function(){...});
*/
Hook.prototype.trigger = function(){
var args = arguments;
var context = this.context;
var promises = [];
/**
* 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, i) {
var executing = task.apply(context, args);
this.hooks.forEach(function(task, i) {
var executing = task.apply(context, args);
if(executing && typeof executing["then"] === "function") {
// Task is a function that returns a promise
promises.push(executing);
}
// Otherwise Task resolves immediately, continue
});
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);
};
return Promise.all(promises);
};
// Adds a function to be run before a hook completes
Hook.prototype.list = function(){
return this.hooks;
};
// Adds a function to be run before a hook completes
list(){
return this.hooks;
};
Hook.prototype.clear = function(){
return this.hooks = [];
};
module.exports = Hook;
clear(){
return this.hooks = [];
};
}
export default Hook;

15
src/index.js Normal file
View file

@ -0,0 +1,15 @@
import Book from './book';
import EpubCFI from './epubcfi';
import Rendition from './rendition';
import Contents from './contents';
import Layout from './layout';
import ePub from './epub';
export default ePub;
export {
Book,
EpubCFI,
Rendition,
Contents,
Layout
};

View file

@ -1,4 +1,4 @@
var core = require('./core');
import core from './utils/core';
/**
* Figures out the CSS to apply for a layout
@ -9,146 +9,148 @@ var core = require('./core');
* @param {[int=800]} settings.minSpreadWidth
* @param {[boolean=false]} settings.evenSpreads
*/
function Layout(settings){
this.name = settings.layout || "reflowable";
this._spread = (settings.spread === "none") ? false : true;
this._minSpreadWidth = settings.minSpreadWidth || 800;
this._evenSpreads = settings.evenSpreads || false;
class Layout {
constructor(settings) {
this.name = settings.layout || "reflowable";
this._spread = (settings.spread === "none") ? false : true;
this._minSpreadWidth = settings.minSpreadWidth || 800;
this._evenSpreads = settings.evenSpreads || false;
if (settings.flow === "scrolled-continuous" ||
settings.flow === "scrolled-doc") {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
if (settings.flow === "scrolled-continuous" ||
settings.flow === "scrolled-doc") {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
this.width = 0;
this.height = 0;
this.spreadWidth = 0;
this.delta = 0;
this.width = 0;
this.height = 0;
this.spreadWidth = 0;
this.delta = 0;
this.columnWidth = 0;
this.gap = 0;
this.divisor = 1;
};
/**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
*/
Layout.prototype.flow = function(flow) {
this._flow = (flow === "paginated") ? "paginated" : "scrolled";
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread true | false
* @param {boolean} min integer in pixels
*/
Layout.prototype.spread = function(spread, min) {
this._spread = (spread === "none") ? false : true;
if (min >= 0) {
this._minSpreadWidth = min;
}
}
/**
* Calculate the dimensions of the pagination
* @param {number} _width [description]
* @param {number} _height [description]
* @param {number} _gap [description]
*/
Layout.prototype.calculate = function(_width, _height, _gap){
var divisor = 1;
var gap = _gap || 0;
//-- Check the width and create even width columns
var fullWidth = Math.floor(_width);
var width = _width;
var section = Math.floor(width / 8);
var colWidth;
var spreadWidth;
var delta;
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
gap = ((section % 2 === 0) ? section : section - 1);
}
if (this.name === "pre-paginated" ) {
gap = 0;
}
//-- Double Page
if(divisor > 1) {
colWidth = (width - gap) / divisor;
} else {
colWidth = width;
}
if (this.name === "pre-paginated" && divisor > 1) {
width = colWidth;
}
spreadWidth = colWidth * divisor;
delta = (colWidth + gap) * divisor;
this.width = width;
this.height = _height;
this.spreadWidth = spreadWidth;
this.delta = delta;
this.columnWidth = colWidth;
this.gap = gap;
this.divisor = divisor;
};
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {[Promise]}
*/
Layout.prototype.format = function(contents){
var formating;
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height);
} else if (this._flow === "paginated") {
formating = contents.columns(this.width, this.height, this.columnWidth, this.gap);
} else { // scrolled
formating = contents.size(this.width, null);
}
return formating; // might be a promise in some View Managers
};
/**
* Count number of pages
* @param {number} totalWidth
* @return {number} spreads
* @return {number} pages
*/
Layout.prototype.count = function(totalWidth) {
// var totalWidth = contents.scrollWidth();
var spreads = Math.ceil( totalWidth / this.spreadWidth);
return {
spreads : spreads,
pages : spreads * this.divisor
this.columnWidth = 0;
this.gap = 0;
this.divisor = 1;
};
};
module.exports = Layout;
/**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
*/
flow(flow) {
this._flow = (flow === "paginated") ? "paginated" : "scrolled";
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread true | false
* @param {boolean} min integer in pixels
*/
spread(spread, min) {
this._spread = (spread === "none") ? false : true;
if (min >= 0) {
this._minSpreadWidth = min;
}
}
/**
* Calculate the dimensions of the pagination
* @param {number} _width [description]
* @param {number} _height [description]
* @param {number} _gap [description]
*/
calculate(_width, _height, _gap){
var divisor = 1;
var gap = _gap || 0;
//-- Check the width and create even width columns
var fullWidth = Math.floor(_width);
var width = _width;
var section = Math.floor(width / 8);
var colWidth;
var spreadWidth;
var delta;
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
gap = ((section % 2 === 0) ? section : section - 1);
}
if (this.name === "pre-paginated" ) {
gap = 0;
}
//-- Double Page
if(divisor > 1) {
colWidth = (width - gap) / divisor;
} else {
colWidth = width;
}
if (this.name === "pre-paginated" && divisor > 1) {
width = colWidth;
}
spreadWidth = colWidth * divisor;
delta = (colWidth + gap) * divisor;
this.width = width;
this.height = _height;
this.spreadWidth = spreadWidth;
this.delta = delta;
this.columnWidth = colWidth;
this.gap = gap;
this.divisor = divisor;
};
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {[Promise]}
*/
format(contents){
var formating;
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height);
} else if (this._flow === "paginated") {
formating = contents.columns(this.width, this.height, this.columnWidth, this.gap);
} else { // scrolled
formating = contents.size(this.width, null);
}
return formating; // might be a promise in some View Managers
};
/**
* Count number of pages
* @param {number} totalWidth
* @return {number} spreads
* @return {number} pages
*/
count(totalWidth) {
// var totalWidth = contents.scrollWidth();
var spreads = Math.ceil( totalWidth / this.spreadWidth);
return {
spreads : spreads,
pages : spreads * this.divisor
};
};
}
export default Layout;

View file

@ -1,257 +1,258 @@
var core = require('./core');
var Queue = require('./queue');
var EpubCFI = require('./epubcfi');
var EventEmitter = require('event-emitter');
import {qs, sprint, locationOf} from './utils/core';
import Queue from './queue';
import EpubCFI from './epubcfi';
import EventEmitter from 'event-emitter';
/**
* Find Locations for a Book
* @param {Spine} spine
* @param {request} request
*/
function Locations(spine, request) {
this.spine = spine;
this.request = request;
class Locations {
constructor(spine, request) {
this.spine = spine;
this.request = request;
this.q = new Queue(this);
this.epubcfi = new EpubCFI();
this.q = new Queue(this);
this.epubcfi = new EpubCFI();
this._locations = [];
this.total = 0;
this._locations = [];
this.total = 0;
this.break = 150;
this.break = 150;
this._current = 0;
this._current = 0;
};
/**
* Load all of sections in the book to generate locations
* @param {int} chars how many chars to split on
* @return {object} locations
*/
Locations.prototype.generate = function(chars) {
if (chars) {
this.break = chars;
}
this.q.pause();
this.spine.each(function(section) {
this.q.enqueue(this.process.bind(this), section);
}.bind(this));
return this.q.run().then(function() {
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
return this._locations;
// console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
}.bind(this));
};
Locations.prototype.createRange = function () {
return {
startContainer: undefined,
startOffset: undefined,
endContainer: undefined,
endOffset: undefined
}
};
Locations.prototype.process = function(section) {
return section.load(this.request)
.then(function(contents) {
var locations = this.parse(contents, section.cfiBase);
this._locations = this._locations.concat(locations);
}.bind(this));
};
Locations.prototype.parse = function(contents, cfiBase, chars) {
var locations = [];
var range;
var doc = contents.ownerDocument;
var body = core.qs(doc, 'body');
var counter = 0;
var prev;
var _break = chars || this.break;
var parser = function(node) {
var len = node.length;
var dist;
var pos = 0;
if (node.textContent.trim().length === 0) {
return false; // continue
}
// Start range
if (counter == 0) {
range = this.createRange();
range.startContainer = node;
range.startOffset = 0;
}
dist = _break - counter;
// Node is smaller than a break,
// skip over it
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
// counter = this.break;
pos += dist;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - _break);
// At End
} else {
// End the previous range
range.endContainer = node;
range.endOffset = pos;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = this.createRange();
range.startContainer = node;
range.startOffset = pos;
dist = _break;
}
}
prev = node;
};
core.sprint(body, parser.bind(this));
/**
* Load all of sections in the book to generate locations
* @param {int} chars how many chars to split on
* @return {object} locations
*/
generate(chars) {
// Close remaining
if (range && range.startContainer && prev) {
range.endContainer = prev;
range.endOffset = prev.length;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
}
if (chars) {
this.break = chars;
}
return locations;
};
this.q.pause();
Locations.prototype.locationFromCfi = function(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return core.locationOf(cfi.start, this._locations, this.epubcfi.compare);
};
this.spine.each(function(section) {
Locations.prototype.percentageFromCfi = function(cfi) {
if(this._locations.length === 0) {
return null;
}
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
this.q.enqueue(this.process.bind(this), section);
Locations.prototype.percentageFromLocation = function(loc) {
if (!loc || !this.total) {
return 0;
}
return (loc / this.total);
};
}.bind(this));
Locations.prototype.cfiFromLocation = function(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(pg);
}
return this.q.run().then(function() {
this.total = this._locations.length-1;
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
return cfi;
};
return this._locations;
// console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
}.bind(this));
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);
};
createRange () {
return {
startContainer: undefined,
startOffset: undefined,
endContainer: undefined,
endOffset: undefined
}
};
Locations.prototype.load = function(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
process(section) {
Locations.prototype.save = function(json){
return JSON.stringify(this._locations);
};
return section.load(this.request)
.then(function(contents) {
var locations = this.parse(contents, section.cfiBase);
this._locations = this._locations.concat(locations);
}.bind(this));
Locations.prototype.getCurrent = function(json){
return this._current;
};
};
Locations.prototype.setCurrent = function(curr){
var loc;
parse(contents, cfiBase, chars) {
var locations = [];
var range;
var doc = contents.ownerDocument;
var body = qs(doc, 'body');
var counter = 0;
var prev;
var _break = chars || this.break;
var parser = function(node) {
var len = node.length;
var dist;
var pos = 0;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if (node.textContent.trim().length === 0) {
return false; // continue
}
if(this._locations.length === 0) {
return;
}
// Start range
if (counter == 0) {
range = this.createRange();
range.startContainer = node;
range.startOffset = 0;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
dist = _break - counter;
this.emit("changed", {
percentage: this.percentageFromLocation(loc)
});
};
// Node is smaller than a break,
// skip over it
if(dist > len){
counter += len;
pos = len;
}
Object.defineProperty(Locations.prototype, 'currentLocation', {
get: function () {
while (pos < len) {
// counter = this.break;
pos += dist;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - _break);
// At End
} else {
// End the previous range
range.endContainer = node;
range.endOffset = pos;
// cfi = section.cfiFromRange(range);
let cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = this.createRange();
range.startContainer = node;
range.startOffset = pos;
dist = _break;
}
}
prev = node;
};
sprint(body, parser.bind(this));
// Close remaining
if (range && range.startContainer && prev) {
range.endContainer = prev;
range.endOffset = prev.length;
// cfi = section.cfiFromRange(range);
let cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
}
return locations;
};
locationFromCfi(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return locationOf(cfi.start, this._locations, this.epubcfi.compare);
};
percentageFromCfi(cfi) {
if(this._locations.length === 0) {
return null;
}
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
percentageFromLocation(loc) {
if (!loc || !this.total) {
return 0;
}
return (loc / this.total);
};
cfiFromLocation(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(pg);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
cfiFromPercentage(value){
var percentage = (value > 1) ? value / 100 : value; // Normalize value to 0-1
var loc = Math.ceil(this.total * percentage);
return this.cfiFromLocation(loc);
};
load(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
save(json){
return JSON.stringify(this._locations);
};
getCurrent(json){
return this._current;
},
set: function (curr) {
};
setCurrent(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.emit("changed", {
percentage: this.percentageFromLocation(loc)
});
};
get currentLocation() {
return this._current;
}
set currentLocation(curr) {
this.setCurrent(curr);
}
});
Locations.prototype.length = function () {
return this._locations.length;
};
length () {
return this._locations.length;
};
}
EventEmitter(Locations.prototype);
module.exports = Locations;
export default Locations;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,231 +1,231 @@
var core = require('../../core');
import {uuid, isNumber, isElement, windowBounds} from '../../utils/core';
function Stage(_options) {
this.settings = _options || {};
this.id = "epubjs-container-" + core.uuid();
class Stage {
constructor(_options) {
this.settings = _options || {};
this.id = "epubjs-container-" + uuid();
this.container = this.create(this.settings);
this.container = this.create(this.settings);
if(this.settings.hidden) {
this.wrapper = this.wrap(this.container);
}
}
/*
* Creates an element to render to.
* Resizes to passed width and height or to the elements size
*/
Stage.prototype.create = function(options){
var height = options.height;// !== false ? options.height : "100%";
var width = options.width;// !== false ? options.width : "100%";
var overflow = options.overflow || false;
var axis = options.axis || "vertical";
if(options.height && core.isNumber(options.height)) {
height = options.height + "px";
}
if(options.width && core.isNumber(options.width)) {
width = options.width + "px";
}
// Create new container element
container = document.createElement("div");
container.id = this.id;
container.classList.add("epub-container");
// Style Element
// container.style.fontSize = "0";
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
if(axis === "horizontal") {
container.style.whiteSpace = "nowrap";
}
if(width){
container.style.width = width;
}
if(height){
container.style.height = height;
}
if (overflow) {
container.style.overflow = overflow;
}
return container;
};
Stage.wrap = function(container) {
var wrapper = document.createElement("div");
wrapper.style.visibility = "hidden";
wrapper.style.overflow = "hidden";
wrapper.style.width = "0";
wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
};
Stage.prototype.getElement = function(_element){
var element;
if(core.isElement(_element)) {
element = _element;
} else if (typeof _element === "string") {
element = document.getElementById(_element);
}
if(!element){
console.error("Not an Element");
return;
}
return element;
};
Stage.prototype.attachTo = function(what){
var element = this.getElement(what);
var base;
if(!element){
return;
}
if(this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
element.appendChild(base);
this.element = element;
return element;
};
Stage.prototype.getContainer = function() {
return this.container;
};
Stage.prototype.onResize = function(func){
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if(!core.isNumber(this.settings.width) ||
!core.isNumber(this.settings.height) ) {
window.addEventListener("resize", func, false);
}
};
Stage.prototype.size = function(width, height){
var bounds;
// var width = _width || this.settings.width;
// var height = _height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if(width === null) {
bounds = this.element.getBoundingClientRect();
if(bounds.width) {
width = bounds.width;
this.container.style.width = bounds.width + "px";
}
}
if(height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if(bounds.height) {
height = bounds.height;
this.container.style.height = bounds.height + "px";
if(this.settings.hidden) {
this.wrapper = this.wrap(this.container);
}
}
if(!core.isNumber(width)) {
bounds = this.container.getBoundingClientRect();
width = bounds.width;
//height = bounds.height;
}
/*
* Creates an element to render to.
* Resizes to passed width and height or to the elements size
*/
create(options){
var height = options.height;// !== false ? options.height : "100%";
var width = options.width;// !== false ? options.width : "100%";
var overflow = options.overflow || false;
var axis = options.axis || "vertical";
if(!core.isNumber(height)) {
bounds = bounds || this.container.getBoundingClientRect();
//width = bounds.width;
height = bounds.height;
}
if(options.height && isNumber(options.height)) {
height = options.height + "px";
}
if(options.width && isNumber(options.width)) {
width = options.width + "px";
}
this.containerStyles = window.getComputedStyle(this.container);
// Create new container element
let container = document.createElement("div");
this.containerPadding = {
left: parseFloat(this.containerStyles["padding-left"]) || 0,
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0,
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
container.id = this.id;
container.classList.add("epub-container");
// Style Element
// container.style.fontSize = "0";
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
if(axis === "horizontal") {
container.style.whiteSpace = "nowrap";
}
if(width){
container.style.width = width;
}
if(height){
container.style.height = height;
}
if (overflow) {
container.style.overflow = overflow;
}
return container;
};
return {
width: width -
this.containerPadding.left -
this.containerPadding.right,
height: height -
this.containerPadding.top -
this.containerPadding.bottom
wrap(container) {
var wrapper = document.createElement("div");
wrapper.style.visibility = "hidden";
wrapper.style.overflow = "hidden";
wrapper.style.width = "0";
wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
};
};
Stage.prototype.bounds = function(){
getElement(_element){
var element;
if(!this.container) {
return core.windowBounds();
} else {
return this.container.getBoundingClientRect();
}
if(isElement(_element)) {
element = _element;
} else if (typeof _element === "string") {
element = document.getElementById(_element);
}
}
if(!element){
console.error("Not an Element");
return;
}
Stage.prototype.getSheet = function(){
var style = document.createElement("style");
return element;
};
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
style.appendChild(document.createTextNode(""));
attachTo(what){
document.head.appendChild(style);
var element = this.getElement(what);
var base;
return style.sheet;
}
if(!element){
return;
}
Stage.prototype.addStyleRules = function(selector, rulesArray){
var scope = "#" + this.id + " ";
var rules = "";
if(this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
if(!this.sheet){
this.sheet = this.getSheet();
}
element.appendChild(base);
rulesArray.forEach(function(set) {
for (var prop in set) {
if(set.hasOwnProperty(prop)) {
rules += prop + ":" + set[prop] + ";";
this.element = element;
return element;
};
getContainer() {
return this.container;
};
onResize(func){
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if(!isNumber(this.settings.width) ||
!isNumber(this.settings.height) ) {
window.addEventListener("resize", func, false);
}
};
size(width, height){
var bounds;
// var width = _width || this.settings.width;
// var height = _height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if(width === null) {
bounds = this.element.getBoundingClientRect();
if(bounds.width) {
width = bounds.width;
this.container.style.width = bounds.width + "px";
}
}
})
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
if(height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if(bounds.height) {
height = bounds.height;
this.container.style.height = bounds.height + "px";
}
}
if(!isNumber(width)) {
bounds = this.container.getBoundingClientRect();
width = bounds.width;
//height = bounds.height;
}
if(!isNumber(height)) {
bounds = bounds || this.container.getBoundingClientRect();
//width = bounds.width;
height = bounds.height;
}
this.containerStyles = window.getComputedStyle(this.container);
this.containerPadding = {
left: parseFloat(this.containerStyles["padding-left"]) || 0,
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0,
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
};
return {
width: width -
this.containerPadding.left -
this.containerPadding.right,
height: height -
this.containerPadding.top -
this.containerPadding.bottom
};
};
bounds(){
if(!this.container) {
return windowBounds();
} else {
return this.container.getBoundingClientRect();
}
}
getSheet(){
var style = document.createElement("style");
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
return style.sheet;
}
addStyleRules(selector, rulesArray){
var scope = "#" + this.id + " ";
var rules = "";
if(!this.sheet){
this.sheet = this.getSheet();
}
rulesArray.forEach(function(set) {
for (var prop in set) {
if(set.hasOwnProperty(prop)) {
rules += prop + ":" + set[prop] + ";";
}
}
})
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
}
}
module.exports = Stage;
export default Stage;

View file

@ -1,165 +1,167 @@
function Views(container) {
this.container = container;
this._views = [];
this.length = 0;
this.hidden = false;
};
class Views {
constructor(container) {
this.container = container;
this._views = [];
this.length = 0;
this.hidden = false;
};
Views.prototype.all = function() {
return this._views;
};
all() {
return this._views;
};
Views.prototype.first = function() {
return this._views[0];
};
first() {
return this._views[0];
};
Views.prototype.last = function() {
return this._views[this._views.length-1];
};
last() {
return this._views[this._views.length-1];
};
Views.prototype.indexOf = function(view) {
return this._views.indexOf(view);
};
indexOf(view) {
return this._views.indexOf(view);
};
Views.prototype.slice = function() {
return this._views.slice.apply(this._views, arguments);
};
slice() {
return this._views.slice.apply(this._views, arguments);
};
Views.prototype.get = function(i) {
return this._views[i];
};
get(i) {
return this._views[i];
};
Views.prototype.append = function(view){
this._views.push(view);
if(this.container){
this.container.appendChild(view.element);
}
this.length++;
return view;
};
Views.prototype.prepend = function(view){
this._views.unshift(view);
if(this.container){
this.container.insertBefore(view.element, this.container.firstChild);
}
this.length++;
return view;
};
Views.prototype.insert = function(view, index) {
this._views.splice(index, 0, view);
if(this.container){
if(index < this.container.children.length){
this.container.insertBefore(view.element, this.container.children[index]);
} else {
append(view){
this._views.push(view);
if(this.container){
this.container.appendChild(view.element);
}
}
this.length++;
return view;
};
this.length++;
return view;
};
prepend(view){
this._views.unshift(view);
if(this.container){
this.container.insertBefore(view.element, this.container.firstChild);
}
this.length++;
return view;
};
Views.prototype.remove = function(view) {
var index = this._views.indexOf(view);
insert(view, index) {
this._views.splice(index, 0, view);
if(index > -1) {
this._views.splice(index, 1);
}
if(this.container){
if(index < this.container.children.length){
this.container.insertBefore(view.element, this.container.children[index]);
} else {
this.container.appendChild(view.element);
}
}
this.length++;
return view;
};
remove(view) {
var index = this._views.indexOf(view);
if(index > -1) {
this._views.splice(index, 1);
}
this.destroy(view);
this.length--;
};
Views.prototype.destroy = function(view) {
if(view.displayed){
view.destroy();
}
if(this.container){
this.container.removeChild(view.element);
}
view = null;
};
// Iterators
Views.prototype.each = function() {
return this._views.forEach.apply(this._views, arguments);
};
Views.prototype.clear = function(){
// Remove all views
var view;
var len = this.length;
if(!this.length) return;
for (var i = 0; i < len; i++) {
view = this._views[i];
this.destroy(view);
}
this._views = [];
this.length = 0;
};
this.length--;
};
Views.prototype.find = function(section){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed && view.section.index == section.index) {
return view;
}
}
};
Views.prototype.displayed = function(){
var displayed = [];
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
destroy(view) {
if(view.displayed){
displayed.push(view);
view.destroy();
}
}
return displayed;
};
Views.prototype.show = function(){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.show();
if(this.container){
this.container.removeChild(view.element);
}
}
this.hidden = false;
};
view = null;
};
Views.prototype.hide = function(){
var view;
var len = this.length;
// Iterators
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.hide();
each() {
return this._views.forEach.apply(this._views, arguments);
};
clear(){
// Remove all views
var view;
var len = this.length;
if(!this.length) return;
for (var i = 0; i < len; i++) {
view = this._views[i];
this.destroy(view);
}
}
this.hidden = true;
};
module.exports = Views;
this._views = [];
this.length = 0;
};
find(section){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed && view.section.index == section.index) {
return view;
}
}
};
displayed(){
var displayed = [];
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
displayed.push(view);
}
}
return displayed;
};
show(){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.show();
}
}
this.hidden = false;
};
hide(){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.hide();
}
}
this.hidden = true;
};
}
export default Views;

File diff suppressed because it is too large Load diff

View file

@ -1,429 +1,431 @@
var EventEmitter = require('event-emitter');
var core = require('../../core');
var EpubCFI = require('../../epubcfi');
var Contents = require('../../contents');
// var URI = require('urijs');
function InlineView(section, options) {
this.settings = core.extend({
ignoreClass : '',
axis: 'vertical',
width: 0,
height: 0,
layout: undefined,
globalLayoutProperties: {},
}, options || {});
this.id = "epubjs-view:" + core.uuid();
this.section = section;
this.index = section.index;
this.element = this.container(this.settings.axis);
this.added = false;
this.displayed = false;
this.rendered = false;
this.width = this.settings.width;
this.height = this.settings.height;
this.fixedWidth = 0;
this.fixedHeight = 0;
// Blank Cfi for Parsing
this.epubcfi = new EpubCFI();
this.layout = this.settings.layout;
// Dom events to listen for
// this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
};
InlineView.prototype.container = function(axis) {
var element = document.createElement('div');
element.classList.add("epub-view");
// if(this.settings.axis === "horizontal") {
// element.style.width = "auto";
// element.style.height = "0";
// } else {
// element.style.width = "0";
// element.style.height = "auto";
// }
element.style.overflow = "hidden";
if(axis && axis == "horizontal"){
element.style.display = "inline-block";
} else {
element.style.display = "block";
}
return element;
};
InlineView.prototype.create = function() {
if(this.frame) {
return this.frame;
}
if(!this.element) {
this.element = this.createContainer();
}
this.frame = document.createElement('div');
this.frame.id = this.id;
this.frame.style.overflow = "hidden";
this.frame.style.wordSpacing = "initial";
this.frame.style.lineHeight = "initial";
this.resizing = true;
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
if(this.settings.axis === "horizontal") {
this.frame.style.width = "auto";
this.frame.style.height = "0";
} else {
this.frame.style.width = "0";
this.frame.style.height = "auto";
}
this._width = 0;
this._height = 0;
this.element.appendChild(this.frame);
this.added = true;
this.elementBounds = core.bounds(this.element);
return this.frame;
};
InlineView.prototype.render = function(request, show) {
// view.onLayout = this.layout.format.bind(this.layout);
this.create();
// Fit to size of the container, apply padding
this.size();
// Render Chain
return this.section.render(request)
.then(function(contents){
return this.load(contents);
}.bind(this))
// .then(function(doc){
// return this.hooks.content.trigger(view, this);
// }.bind(this))
.then(function(){
// this.settings.layout.format(view.contents);
// return this.hooks.layout.trigger(view, this);
}.bind(this))
// .then(function(){
// return this.display();
// }.bind(this))
// .then(function(){
// return this.hooks.render.trigger(view, this);
// }.bind(this))
.then(function(){
// apply the layout function to the contents
this.settings.layout.format(this.contents);
// Expand the iframe to the full size of the content
// this.expand();
// Listen for events that require an expansion of the iframe
this.addListeners();
if(show !== false) {
//this.q.enqueue(function(view){
this.show();
//}, view);
}
// this.map = new Map(view, this.layout);
//this.hooks.show.trigger(view, this);
this.emit("rendered", this.section);
}.bind(this))
.catch(function(e){
this.emit("loaderror", e);
}.bind(this));
};
// Determine locks base on settings
InlineView.prototype.size = function(_width, _height) {
var width = _width || this.settings.width;
var height = _height || this.settings.height;
if(this.layout.name === "pre-paginated") {
// TODO: check if these are different than the size set in chapter
this.lock("both", width, height);
} else if(this.settings.axis === "horizontal") {
this.lock("height", width, height);
} else {
this.lock("width", width, height);
}
};
// Lock an axis to element dimensions, taking borders into account
InlineView.prototype.lock = function(what, width, height) {
var elBorders = core.borders(this.element);
var iframeBorders;
if(this.frame) {
iframeBorders = core.borders(this.frame);
} else {
iframeBorders = {width: 0, height: 0};
}
if(what == "width" && core.isNumber(width)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.resize(this.lockedWidth, false); // width keeps ratio correct
}
if(what == "height" && core.isNumber(height)){
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(false, this.lockedHeight);
}
if(what === "both" &&
core.isNumber(width) &&
core.isNumber(height)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(this.lockedWidth, this.lockedHeight);
}
};
// Resize a single axis based on content dimensions
InlineView.prototype.expand = function(force) {
var width = this.lockedWidth;
var height = this.lockedHeight;
var textWidth, textHeight;
if(!this.frame || this._expanding) return;
this._expanding = true;
// Expand Horizontally
if(this.settings.axis === "horizontal") {
width = this.contentWidth(textWidth);
} // Expand Vertically
else if(this.settings.axis === "vertical") {
height = this.contentHeight(textHeight);
}
// Only Resize if dimensions have changed or
// if Frame is still hidden, so needs reframing
if(this._needsReframe || width != this._width || height != this._height){
this.resize(width, height);
}
this._expanding = false;
};
InlineView.prototype.contentWidth = function(min) {
return this.frame.scrollWidth;
};
InlineView.prototype.contentHeight = function(min) {
console.log(this.frame.scrollHeight);
return this.frame.scrollHeight;
};
InlineView.prototype.resize = function(width, height) {
if(!this.frame) return;
if(core.isNumber(width)){
this.frame.style.width = width + "px";
this._width = width;
}
if(core.isNumber(height)){
this.frame.style.height = height + "px";
this._height = height;
}
this.prevBounds = this.elementBounds;
this.elementBounds = core.bounds(this.element);
size = {
width: this.elementBounds.width,
height: this.elementBounds.height,
widthDelta: this.elementBounds.width - this.prevBounds.width,
heightDelta: this.elementBounds.height - this.prevBounds.height,
};
this.onResize(this, size);
this.emit("resized", size);
};
InlineView.prototype.load = function(contents) {
var loading = new core.defer();
var loaded = loading.promise;
var doc = core.parse(contents, "text/html");
var body = core.qs(doc, "body");
var srcs = doc.querySelectorAll("[src]");
Array.prototype.slice.call(srcs)
.forEach(function(item) {
var src = item.getAttribute('src');
var assetUri = URI(src);
var origin = assetUri.origin();
var absoluteUri;
if (!origin) {
absoluteUri = assetUri.absoluteTo(this.section.url);
item.src = absoluteUri;
}
}.bind(this));
this.frame.innerHTML = body.innerHTML;
this.document = this.frame.ownerDocument;
this.window = this.document.defaultView;
this.contents = new Contents(this.document, this.frame);
this.rendering = false;
loading.resolve(this.contents);
return loaded;
};
InlineView.prototype.setLayout = function(layout) {
this.layout = layout;
};
InlineView.prototype.resizeListenters = function() {
// Test size again
// clearTimeout(this.expanding);
// this.expanding = setTimeout(this.expand.bind(this), 350);
};
InlineView.prototype.addListeners = function() {
//TODO: Add content listeners for expanding
};
InlineView.prototype.removeListeners = function(layoutFunc) {
//TODO: remove content listeners for expanding
};
InlineView.prototype.display = function(request) {
var displayed = new core.defer();
if (!this.displayed) {
this.render(request).then(function () {
this.emit("displayed", this);
this.onDisplayed(this);
this.displayed = true;
displayed.resolve(this);
}.bind(this));
} else {
displayed.resolve(this);
}
return displayed.promise;
};
InlineView.prototype.show = function() {
this.element.style.visibility = "visible";
if(this.frame){
this.frame.style.visibility = "visible";
}
this.emit("shown", this);
};
InlineView.prototype.hide = function() {
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.stopExpanding = true;
this.emit("hidden", this);
};
InlineView.prototype.position = function() {
return this.element.getBoundingClientRect();
};
InlineView.prototype.locationOf = function(target) {
var parentPos = this.frame.getBoundingClientRect();
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
return {
"left": window.scrollX + parentPos.left + targetPos.left,
"top": window.scrollY + parentPos.top + targetPos.top
};
};
InlineView.prototype.onDisplayed = function(view) {
// Stub, override with a custom functions
};
InlineView.prototype.onResize = function(view, e) {
// Stub, override with a custom functions
};
InlineView.prototype.bounds = function() {
if(!this.elementBounds) {
this.elementBounds = core.bounds(this.element);
}
return this.elementBounds;
};
InlineView.prototype.destroy = function() {
if(this.displayed){
import EventEmitter from 'event-emitter';
import {extend, borders, uuid, isNumber, bounds, defer} from '../../utils/core';
import EpubCFI from '../../epubcfi';
import Contents from '../../contents';
// import URI from 'urijs';
class InlineView {
constructor(section, options) {
this.settings = extend({
ignoreClass : '',
axis: 'vertical',
width: 0,
height: 0,
layout: undefined,
globalLayoutProperties: {},
}, options || {});
this.id = "epubjs-view:" + uuid();
this.section = section;
this.index = section.index;
this.element = this.container(this.settings.axis);
this.added = false;
this.displayed = false;
this.rendered = false;
this.removeListeners();
this.width = this.settings.width;
this.height = this.settings.height;
this.fixedWidth = 0;
this.fixedHeight = 0;
// Blank Cfi for Parsing
this.epubcfi = new EpubCFI();
this.layout = this.settings.layout;
// Dom events to listen for
// this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
};
container(axis) {
var element = document.createElement('div');
element.classList.add("epub-view");
// if(this.settings.axis === "horizontal") {
// element.style.width = "auto";
// element.style.height = "0";
// } else {
// element.style.width = "0";
// element.style.height = "auto";
// }
element.style.overflow = "hidden";
if(axis && axis == "horizontal"){
element.style.display = "inline-block";
} else {
element.style.display = "block";
}
return element;
};
create() {
if(this.frame) {
return this.frame;
}
if(!this.element) {
this.element = this.createContainer();
}
this.frame = document.createElement('div');
this.frame.id = this.id;
this.frame.style.overflow = "hidden";
this.frame.style.wordSpacing = "initial";
this.frame.style.lineHeight = "initial";
this.resizing = true;
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
if(this.settings.axis === "horizontal") {
this.frame.style.width = "auto";
this.frame.style.height = "0";
} else {
this.frame.style.width = "0";
this.frame.style.height = "auto";
}
this._width = 0;
this._height = 0;
this.element.appendChild(this.frame);
this.added = true;
this.elementBounds = bounds(this.element);
return this.frame;
};
render(request, show) {
// view.onLayout = this.layout.format.bind(this.layout);
this.create();
// Fit to size of the container, apply padding
this.size();
// Render Chain
return this.section.render(request)
.then(function(contents){
return this.load(contents);
}.bind(this))
// .then(function(doc){
// return this.hooks.content.trigger(view, this);
// }.bind(this))
.then(function(){
// this.settings.layout.format(view.contents);
// return this.hooks.layout.trigger(view, this);
}.bind(this))
// .then(function(){
// return this.display();
// }.bind(this))
// .then(function(){
// return this.hooks.render.trigger(view, this);
// }.bind(this))
.then(function(){
// apply the layout function to the contents
this.settings.layout.format(this.contents);
// Expand the iframe to the full size of the content
// this.expand();
// Listen for events that require an expansion of the iframe
this.addListeners();
if(show !== false) {
//this.q.enqueue(function(view){
this.show();
//}, view);
}
// this.map = new Map(view, this.layout);
//this.hooks.show.trigger(view, this);
this.emit("rendered", this.section);
}.bind(this))
.catch(function(e){
this.emit("loaderror", e);
}.bind(this));
};
// Determine locks base on settings
size(_width, _height) {
var width = _width || this.settings.width;
var height = _height || this.settings.height;
if(this.layout.name === "pre-paginated") {
// TODO: check if these are different than the size set in chapter
this.lock("both", width, height);
} else if(this.settings.axis === "horizontal") {
this.lock("height", width, height);
} else {
this.lock("width", width, height);
}
};
// Lock an axis to element dimensions, taking borders into account
lock(what, width, height) {
var elBorders = borders(this.element);
var iframeBorders;
if(this.frame) {
iframeBorders = borders(this.frame);
} else {
iframeBorders = {width: 0, height: 0};
}
if(what == "width" && isNumber(width)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.resize(this.lockedWidth, false); // width keeps ratio correct
}
if(what == "height" && isNumber(height)){
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(false, this.lockedHeight);
}
if(what === "both" &&
isNumber(width) &&
isNumber(height)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(this.lockedWidth, this.lockedHeight);
}
};
// Resize a single axis based on content dimensions
expand(force) {
var width = this.lockedWidth;
var height = this.lockedHeight;
var textWidth, textHeight;
if(!this.frame || this._expanding) return;
this._expanding = true;
// Expand Horizontally
if(this.settings.axis === "horizontal") {
width = this.contentWidth(textWidth);
} // Expand Vertically
else if(this.settings.axis === "vertical") {
height = this.contentHeight(textHeight);
}
// Only Resize if dimensions have changed or
// if Frame is still hidden, so needs reframing
if(this._needsReframe || width != this._width || height != this._height){
this.resize(width, height);
}
this._expanding = false;
};
contentWidth(min) {
return this.frame.scrollWidth;
};
contentHeight(min) {
console.log(this.frame.scrollHeight);
return this.frame.scrollHeight;
};
resize(width, height) {
if(!this.frame) return;
if(isNumber(width)){
this.frame.style.width = width + "px";
this._width = width;
}
if(isNumber(height)){
this.frame.style.height = height + "px";
this._height = height;
}
this.prevBounds = this.elementBounds;
this.elementBounds = bounds(this.element);
size = {
width: this.elementBounds.width,
height: this.elementBounds.height,
widthDelta: this.elementBounds.width - this.prevBounds.width,
heightDelta: this.elementBounds.height - this.prevBounds.height,
};
this.onResize(this, size);
this.emit("resized", size);
};
load(contents) {
var loading = new defer();
var loaded = loading.promise;
var doc = parse(contents, "text/html");
var body = qs(doc, "body");
var srcs = doc.querySelectorAll("[src]");
Array.prototype.slice.call(srcs)
.forEach(function(item) {
var src = item.getAttribute('src');
var assetUri = URI(src);
var origin = assetUri.origin();
var absoluteUri;
if (!origin) {
absoluteUri = assetUri.absoluteTo(this.section.url);
item.src = absoluteUri;
}
}.bind(this));
this.frame.innerHTML = body.innerHTML;
this.document = this.frame.ownerDocument;
this.window = this.document.defaultView;
this.contents = new Contents(this.document, this.frame);
this.rendering = false;
loading.resolve(this.contents);
return loaded;
};
setLayout(layout) {
this.layout = layout;
};
resizeListenters() {
// Test size again
// clearTimeout(this.expanding);
// this.expanding = setTimeout(this.expand.bind(this), 350);
};
addListeners() {
//TODO: Add content listeners for expanding
};
removeListeners(layoutFunc) {
//TODO: remove content listeners for expanding
};
display(request) {
var displayed = new defer();
if (!this.displayed) {
this.render(request).then(function () {
this.emit("displayed", this);
this.onDisplayed(this);
this.displayed = true;
displayed.resolve(this);
}.bind(this));
} else {
displayed.resolve(this);
}
return displayed.promise;
};
show() {
this.element.style.visibility = "visible";
if(this.frame){
this.frame.style.visibility = "visible";
}
this.emit("shown", this);
};
hide() {
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.stopExpanding = true;
this.element.removeChild(this.frame);
this.displayed = false;
this.frame = null;
this.emit("hidden", this);
};
this._textWidth = null;
this._textHeight = null;
this._width = null;
this._height = null;
}
// this.element.style.height = "0px";
// this.element.style.width = "0px";
};
position() {
return this.element.getBoundingClientRect();
};
locationOf(target) {
var parentPos = this.frame.getBoundingClientRect();
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
return {
"left": window.scrollX + parentPos.left + targetPos.left,
"top": window.scrollY + parentPos.top + targetPos.top
};
};
onDisplayed(view) {
// Stub, override with a custom functions
};
onResize(view, e) {
// Stub, override with a custom functions
};
bounds() {
if(!this.elementBounds) {
this.elementBounds = bounds(this.element);
}
return this.elementBounds;
};
destroy() {
if(this.displayed){
this.displayed = false;
this.removeListeners();
this.stopExpanding = true;
this.element.removeChild(this.frame);
this.displayed = false;
this.frame = null;
this._textWidth = null;
this._textHeight = null;
this._width = null;
this._height = null;
}
// this.element.style.height = "0px";
// this.element.style.width = "0px";
};
}
EventEmitter(InlineView.prototype);
module.exports = InlineView;
export default InlineView;

View file

@ -1,297 +1,299 @@
var EpubCFI = require('./epubcfi');
import EpubCFI from './epubcfi';
function Mapping(layout){
this.layout = layout;
};
Mapping.prototype.section = function(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
return map;
};
Mapping.prototype.page = function(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
if (!root) {
return;
}
return this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end)
});
};
Mapping.prototype.walk = function(root, func) {
//var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if ( node.data.trim().length > 0 ) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
}, false);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if(result) break;
}
return result;
};
Mapping.prototype.findRanges = function(view){
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var count = this.layout.count(scrollWidth);
var column = this.layout.column;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (column + gap) * i;
end = (column * (i+1)) + (gap * i);
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end)
});
}
return columns;
};
Mapping.prototype.findStart = function(root, start, end){
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if( left >= start && left <= end ) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
};
Mapping.prototype.findEnd = function(root, start, end){
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if(left > end && $prev) {
return $prev;
} else if(right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found){
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
};
Mapping.prototype.findTextStartRange = function(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if( pos.left >= start ) {
return range;
}
prev = range;
}
return ranges[0];
};
Mapping.prototype.findTextEndRange = function(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if(pos.left > end && prev) {
return prev;
} else if(pos.right > end) {
return range;
}
prev = range;
}
// Ends before limit
return ranges[ranges.length-1];
};
Mapping.prototype.splitTextNodeIntoRanges = function(node, _splitter){
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var rect;
var list;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
pos = text.indexOf(splitter);
if(pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while ( pos != -1 ) {
pos = text.indexOf(splitter, pos + 1);
if(pos > 0) {
if(range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos+1);
}
}
if(range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
};
Mapping.prototype.rangePairToCfiPair = function(cfiBase, rangePair){
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(true);
// startCfi = section.cfiFromRange(startRange);
// endCfi = section.cfiFromRange(endRange);
startCfi = new EpubCFI(startRange, cfiBase).toString();
endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi
class Mapping {
constructor(layout) {
this.layout = layout;
};
};
section(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
Mapping.prototype.rangeListToCfiList = function(cfiBase, columns){
var map = [];
var rangePair, cifPair;
return map;
};
for (var i = 0; i < columns.length; i++) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]);
page(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
map.push(cifPair);
if (!root) {
return;
}
}
return this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end)
});
};
return map;
};
walk(root, func) {
//var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if ( node.data.trim().length > 0 ) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
}, false);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if(result) break;
}
module.exports = Mapping;
return result;
};
findRanges(view){
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var count = this.layout.count(scrollWidth);
var column = this.layout.column;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (column + gap) * i;
end = (column * (i+1)) + (gap * i);
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end)
});
}
return columns;
};
findStart(root, start, end){
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if( left >= start && left <= end ) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
};
findEnd(root, start, end){
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if(left > end && $prev) {
return $prev;
} else if(right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found){
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
};
findTextStartRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if( pos.left >= start ) {
return range;
}
prev = range;
}
return ranges[0];
};
findTextEndRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if(pos.left > end && prev) {
return prev;
} else if(pos.right > end) {
return range;
}
prev = range;
}
// Ends before limit
return ranges[ranges.length-1];
};
splitTextNodeIntoRanges(node, _splitter){
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var rect;
var list;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
var pos = text.indexOf(splitter);
if(pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while ( pos != -1 ) {
pos = text.indexOf(splitter, pos + 1);
if(pos > 0) {
if(range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos+1);
}
}
if(range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
};
rangePairToCfiPair(cfiBase, rangePair){
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(true);
// startCfi = section.cfiFromRange(startRange);
// endCfi = section.cfiFromRange(endRange);
let startCfi = new EpubCFI(startRange, cfiBase).toString();
let endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi
};
};
rangeListToCfiList(cfiBase, columns){
var map = [];
var rangePair, cifPair;
for (var i = 0; i < columns.length; i++) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]);
map.push(cifPair);
}
return map;
};
}
export default Mapping;

View file

@ -1,200 +1,201 @@
var core = require('./core');
var path = require('path');
import {qs, qsa, querySelectorByType} from './utils/core';
/**
* Navigation Parser
* @param {document} xml navigation html / xhtml / ncx
*/
function Navigation(xml){
this.toc = [];
this.tocByHref = {};
this.tocById = {};
class Navigation {
constructor(xml) {
this.toc = [];
this.tocByHref = {};
this.tocById = {};
if (xml) {
this.parse(xml);
}
};
/**
* Parse out the navigation items
* @param {document} xml navigation html / xhtml / ncx
*/
Navigation.prototype.parse = function(xml) {
var html = core.qs(xml, "html");
var ncx = core.qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){
this.toc = this.parseNcx(xml);
}
this.unpack(this.toc);
};
/**
* Unpack navigation items
* @private
* @param {array} toc
*/
Navigation.prototype.unpack = function(toc) {
var item;
for (var i = 0; i < toc.length; i++) {
item = toc[i];
this.tocByHref[item.href] = i;
this.tocById[item.id] = i;
}
};
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItems
*/
Navigation.prototype.get = function(target) {
var index;
if(!target) {
return this.toc;
}
if(target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if(target in this.tocByHref){
index = this.tocByHref[target];
}
return this.toc[index];
};
/**
* Parse from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
Navigation.prototype.parseNav = function(navHtml){
var navElement = core.querySelectorByType(navHtml, "nav", "toc");
var navItems = navElement ? core.qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.navItem(navItems[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
if (xml) {
this.parse(xml);
}
}
return list;
};
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
Navigation.prototype.navItem = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "a"),
src = content.getAttribute('href') || '',
text = content.textContent || "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
};
/**
* Parse from a Epub > 3.0 NC
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
Navigation.prototype.parseNcx = function(tocXml){
var navPoints = core.qsa(tocXml, "navPoint");
var length = navPoints.length;
var i;
var toc = {};
var list = [];
var item, parent;
/**
* Parse out the navigation items
* @param {document} xml navigation html / xhtml / ncx
*/
parse(xml) {
var html = qs(xml, "html");
var ncx = qs(xml, "ncx");
if(!navPoints || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.ncxItem(navPoints[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){
this.toc = this.parseNcx(xml);
}
}
return list;
};
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
Navigation.prototype.ncxItem = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "content"),
src = content.getAttribute('src'),
navLabel = core.qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
this.unpack(this.toc);
};
};
/**
* forEach pass through
* @param {Function} fn function to run on each item
* @return {method} forEach loop
*/
Navigation.prototype.forEach = function(fn) {
return this.toc.forEach(fn);
};
/**
* Unpack navigation items
* @private
* @param {array} toc
*/
unpack(toc) {
var item;
module.exports = Navigation;
for (var i = 0; i < toc.length; i++) {
item = toc[i];
this.tocByHref[item.href] = i;
this.tocById[item.id] = i;
}
};
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItems
*/
get(target) {
var index;
if(!target) {
return this.toc;
}
if(target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if(target in this.tocByHref){
index = this.tocByHref[target];
}
return this.toc[index];
};
/**
* Parse from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "toc");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.navItem(navItems[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
return list;
};
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
navItem(item){
var id = item.getAttribute('id') || false,
content = qs(item, "a"),
src = content.getAttribute('href') || '',
text = content.textContent || "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
};
/**
* Parse from a Epub > 3.0 NC
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNcx(tocXml){
var navPoints = qsa(tocXml, "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.ncxItem(navPoints[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
return list;
};
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
ncxItem(item){
var id = item.getAttribute('id') || false,
content = qs(item, "content"),
src = content.getAttribute('src'),
navLabel = qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
};
/**
* forEach pass through
* @param {Function} fn function to run on each item
* @return {method} forEach loop
*/
forEach(fn) {
return this.toc.forEach(fn);
};
}
export default Navigation;

View file

@ -1,282 +1,283 @@
var path = require('path');
var core = require('./core');
var EpubCFI = require('./epubcfi');
import {qs, qsa, qsp} from './utils/core';
import EpubCFI from './epubcfi';
/**
* Open Packaging Format Parser
* @class
* @param {document} packageDocument OPF XML
*/
function Packaging(packageDocument) {
if (packageDocument) {
this.parse(packageDocument);
}
};
/**
* Parse OPF XML
* @param {document} packageDocument OPF XML
* @return {object} parsed package parts
*/
Packaging.prototype.parse = function(packageDocument){
var metadataNode, manifestNode, spineNode;
if(!packageDocument) {
console.error("Package File Not Found");
return;
}
metadataNode = core.qs(packageDocument, "metadata");
if(!metadataNode) {
console.error("No Metadata Found");
return;
}
manifestNode = core.qs(packageDocument, "manifest");
if(!manifestNode) {
console.error("No Manifest Found");
return;
}
spineNode = core.qs(packageDocument, "spine");
if(!spineNode) {
console.error("No Spine Found");
return;
}
this.manifest = this.parseManifest(manifestNode);
this.navPath = this.findNavPath(manifestNode);
this.ncxPath = this.findNcxPath(manifestNode, spineNode);
this.coverPath = this.findCoverPath(packageDocument);
this.spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
this.spine = this.parseSpine(spineNode, this.manifest);
this.metadata = this.parseMetadata(metadataNode);
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
return {
'metadata' : this.metadata,
'spine' : this.spine,
'manifest' : this.manifest,
'navPath' : this.navPath,
'ncxPath' : this.ncxPath,
'coverPath': this.coverPath,
'spineNodeIndex' : this.spineNodeIndex
};
};
/**
* Parse Metadata
* @private
* @param {document} xml
* @return {object} metadata
*/
Packaging.prototype.parseMetadata = function(xml){
var metadata = {};
metadata.title = this.getElementText(xml, 'title');
metadata.creator = this.getElementText(xml, 'creator');
metadata.description = this.getElementText(xml, 'description');
metadata.pubdate = this.getElementText(xml, 'date');
metadata.publisher = this.getElementText(xml, 'publisher');
metadata.identifier = this.getElementText(xml, "identifier");
metadata.language = this.getElementText(xml, "language");
metadata.rights = this.getElementText(xml, "rights");
metadata.modified_date = this.getPropertyText(xml, 'dcterms:modified');
metadata.layout = this.getPropertyText(xml, "rendition:layout");
metadata.orientation = this.getPropertyText(xml, 'rendition:orientation');
metadata.flow = this.getPropertyText(xml, 'rendition:flow');
metadata.viewport = this.getPropertyText(xml, 'rendition:viewport');
// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
return metadata;
};
/**
* Parse Manifest
* @private
* @param {document} manifestXml
* @return {object} manifest
*/
Packaging.prototype.parseManifest = function(manifestXml){
var manifest = {};
//-- Turn items into an array
// var selected = manifestXml.querySelectorAll("item");
var selected = core.qsa(manifestXml, "item");
var 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' : href,
'type' : type,
'properties' : properties.length ? properties.split(' ') : []
};
});
return manifest;
};
/**
* Parse Spine
* @param {document} spineXml
* @param {Packaging.manifest} manifest
* @return {object} spine
*/
Packaging.prototype.parseSpine = function(spineXml, manifest){
var spine = [];
var selected = spineXml.getElementsByTagName("itemref"),
items = Array.prototype.slice.call(selected);
var epubcfi = new EpubCFI();
//-- Add to array to mantain ordering and cross reference with manifest
items.forEach(function(item, index){
var idref = 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 itemref = {
'idref' : idref,
'linear' : item.getAttribute('linear') || '',
'properties' : propArray,
// 'href' : manifest[Id].href,
// 'url' : manifest[Id].url,
'index' : index
// 'cfiBase' : cfiBase
};
spine.push(itemref);
});
return spine;
};
/**
* Find TOC NAV
* @private
*/
Packaging.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 ']");
var node = core.qsp(manifestNode, "item", {"properties":"nav"});
return node ? node.getAttribute('href') : false;
};
/**
* Find TOC NCX
* media-type="application/x-dtbncx+xml" href="toc.ncx"
* @private
*/
Packaging.prototype.findNcxPath = function(manifestNode, spineNode){
// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
var node = core.qsp(manifestNode, "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 + "']");
node = manifestNode.getElementById(tocId);
class Packaging {
constructor(packageDocument) {
if (packageDocument) {
this.parse(packageDocument);
}
}
};
return node ? node.getAttribute('href') : false;
};
/**
* Parse OPF XML
* @param {document} packageDocument OPF XML
* @return {object} parsed package parts
*/
parse(packageDocument){
var metadataNode, manifestNode, spineNode;
/**
* Find the Cover Path
* <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
* Fallback for Epub 2.0
* @param {document} packageXml
* @return {string} href
*/
Packaging.prototype.findCoverPath = function(packageXml){
var pkg = core.qs(packageXml, "package");
var epubVersion = pkg.getAttribute('version');
if(!packageDocument) {
console.error("Package File Not Found");
return;
}
if (epubVersion === '2.0') {
var metaCover = core.qsp(packageXml, 'meta', {'name':'cover'});
if (metaCover) {
var coverId = metaCover.getAttribute('content');
// var cover = packageXml.querySelector("item[id='" + coverId + "']");
var cover = packageXml.getElementById(coverId);
return cover ? cover.getAttribute('href') : '';
metadataNode = qs(packageDocument, "metadata");
if(!metadataNode) {
console.error("No Metadata Found");
return;
}
manifestNode = qs(packageDocument, "manifest");
if(!manifestNode) {
console.error("No Manifest Found");
return;
}
spineNode = qs(packageDocument, "spine");
if(!spineNode) {
console.error("No Spine Found");
return;
}
this.manifest = this.parseManifest(manifestNode);
this.navPath = this.findNavPath(manifestNode);
this.ncxPath = this.findNcxPath(manifestNode, spineNode);
this.coverPath = this.findCoverPath(packageDocument);
this.spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
this.spine = this.parseSpine(spineNode, this.manifest);
this.metadata = this.parseMetadata(metadataNode);
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
return {
'metadata' : this.metadata,
'spine' : this.spine,
'manifest' : this.manifest,
'navPath' : this.navPath,
'ncxPath' : this.ncxPath,
'coverPath': this.coverPath,
'spineNodeIndex' : this.spineNodeIndex
};
};
/**
* Parse Metadata
* @private
* @param {document} xml
* @return {object} metadata
*/
parseMetadata(xml){
var metadata = {};
metadata.title = this.getElementText(xml, 'title');
metadata.creator = this.getElementText(xml, 'creator');
metadata.description = this.getElementText(xml, 'description');
metadata.pubdate = this.getElementText(xml, 'date');
metadata.publisher = this.getElementText(xml, 'publisher');
metadata.identifier = this.getElementText(xml, "identifier");
metadata.language = this.getElementText(xml, "language");
metadata.rights = this.getElementText(xml, "rights");
metadata.modified_date = this.getPropertyText(xml, 'dcterms:modified');
metadata.layout = this.getPropertyText(xml, "rendition:layout");
metadata.orientation = this.getPropertyText(xml, 'rendition:orientation');
metadata.flow = this.getPropertyText(xml, 'rendition:flow');
metadata.viewport = this.getPropertyText(xml, 'rendition:viewport');
// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
return metadata;
};
/**
* Parse Manifest
* @private
* @param {document} manifestXml
* @return {object} manifest
*/
parseManifest(manifestXml){
var manifest = {};
//-- Turn items into an array
// var selected = manifestXml.querySelectorAll("item");
var selected = qsa(manifestXml, "item");
var 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' : href,
'type' : type,
'properties' : properties.length ? properties.split(' ') : []
};
});
return manifest;
};
/**
* Parse Spine
* @param {document} spineXml
* @param {Packaging.manifest} manifest
* @return {object} spine
*/
parseSpine(spineXml, manifest){
var spine = [];
var selected = spineXml.getElementsByTagName("itemref"),
items = Array.prototype.slice.call(selected);
var epubcfi = new EpubCFI();
//-- Add to array to mantain ordering and cross reference with manifest
items.forEach(function(item, index){
var idref = 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 itemref = {
'idref' : idref,
'linear' : item.getAttribute('linear') || '',
'properties' : propArray,
// 'href' : manifest[Id].href,
// 'url' : manifest[Id].url,
'index' : index
// 'cfiBase' : cfiBase
};
spine.push(itemref);
});
return spine;
};
/**
* Find TOC NAV
* @private
*/
findNavPath(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 ']");
var node = qsp(manifestNode, "item", {"properties":"nav"});
return node ? node.getAttribute('href') : false;
};
/**
* Find TOC NCX
* media-type="application/x-dtbncx+xml" href="toc.ncx"
* @private
*/
findNcxPath(manifestNode, spineNode){
// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
var node = qsp(manifestNode, "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 + "']");
node = manifestNode.getElementById(tocId);
}
}
return node ? node.getAttribute('href') : false;
};
/**
* Find the Cover Path
* <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
* Fallback for Epub 2.0
* @param {document} packageXml
* @return {string} href
*/
findCoverPath(packageXml){
var pkg = qs(packageXml, "package");
var epubVersion = pkg.getAttribute('version');
if (epubVersion === '2.0') {
var metaCover = qsp(packageXml, 'meta', {'name':'cover'});
if (metaCover) {
var coverId = metaCover.getAttribute('content');
// var cover = packageXml.querySelector("item[id='" + coverId + "']");
var cover = packageXml.getElementById(coverId);
return cover ? cover.getAttribute('href') : '';
}
else {
return false;
}
}
else {
return false;
// var node = packageXml.querySelector("item[properties='cover-image']");
var node = qsp(packageXml, 'item', {'properties':'cover-image'});
return node ? node.getAttribute('href') : '';
}
}
else {
// var node = packageXml.querySelector("item[properties='cover-image']");
var node = core.qsp(packageXml, 'item', {'properties':'cover-image'});
return node ? node.getAttribute('href') : '';
}
};
};
/**
* Get text of a namespaced element
* @private
* @param {document} xml
* @param {string} tag
* @return {string} text
*/
Packaging.prototype.getElementText = function(xml, tag){
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
el;
/**
* Get text of a namespaced element
* @private
* @param {document} xml
* @param {string} tag
* @return {string} text
*/
getElementText(xml, tag){
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
el;
if(!found || found.length === 0) return '';
if(!found || found.length === 0) return '';
el = found[0];
el = found[0];
if(el.childNodes.length){
return el.childNodes[0].nodeValue;
}
if(el.childNodes.length){
return el.childNodes[0].nodeValue;
}
return '';
return '';
};
};
/**
* Get text by property
* @private
* @param {document} xml
* @param {string} property
* @return {string} text
*/
Packaging.prototype.getPropertyText = function(xml, property){
var el = core.qsp(xml, "meta", {"property":property});
/**
* Get text by property
* @private
* @param {document} xml
* @param {string} property
* @return {string} text
*/
getPropertyText(xml, property){
var el = qsp(xml, "meta", {"property":property});
if(el && el.childNodes.length){
return el.childNodes[0].nodeValue;
}
if(el && el.childNodes.length){
return el.childNodes[0].nodeValue;
}
return '';
};
return '';
};
}
module.exports = Packaging;
export default Packaging;

View file

@ -1,249 +1,257 @@
var EpubCFI = require('./epubcfi');
var core = require('./core');
import EpubCFI from './epubcfi';
import {
qs,
qsa,
querySelectorByType,
indexOfSorted,
locationOf
} from './utils/core';
/**
* Page List Parser
* @param {[document]} xml
*/
function PageList(xml) {
this.pages = [];
this.locations = [];
this.epubcfi = new EpubCFI();
class PageList {
constructor(xml) {
this.pages = [];
this.locations = [];
this.epubcfi = new EpubCFI();
if (xml) {
this.pageList = this.parse(xml);
}
if(this.pageList && this.pageList.length) {
this.process(this.pageList);
}
};
/**
* Parse PageList Xml
* @param {document} xml
*/
PageList.prototype.parse = function(xml) {
var html = core.qs(xml, "html");
// var ncx = core.qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){ // Not supported
// this.toc = this.parseNcx(xml);
return;
}
};
/**
* Parse a Nav PageList
* @private
* @param {document} navHtml
* @return {PageList.item[]} list
*/
PageList.prototype.parseNav = function(navHtml){
var navElement = core.querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? core.qsa(navElement, "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.item(navItems[i]);
list.push(item);
}
return list;
};
/**
* Page List Item
* @private
* @param {object} item
* @return {object} pageListItem
*/
PageList.prototype.item = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "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
};
}
};
/**
* Process pageList items
* @private
* @param {array} pageList
*/
PageList.prototype.process = function(pageList){
pageList.forEach(function(item){
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
if (xml) {
this.pageList = this.parse(xml);
}
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length-1]);
this.totalPages = this.lastPage - this.firstPage;
};
/**
* Replace HREFs with CFI
* TODO: implement getting CFI from Href
*/
PageList.prototype.addCFIs = function() {
this.pageList.forEach(function(pg){
if(!pg.cfi) {
// epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
// pg.cfi = cfi;
// pg.packageUrl = book.settings.packageUrl;
// });
if(this.pageList && this.pageList.length) {
this.process(this.pageList);
}
});
};
/**
* Parse PageList Xml
* @param {document} xml
*/
parse(xml) {
var html = qs(xml, "html");
// var ncx = qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){ // Not supported
// this.toc = this.parseNcx(xml);
return;
}
};
/**
* Parse a Nav PageList
* @private
* @param {document} navHtml
* @return {PageList.item[]} list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? qsa(navElement, "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.item(navItems[i]);
list.push(item);
}
return list;
};
/**
* Page List Item
* @private
* @param {object} item
* @return {object} pageListItem
*/
item(item){
var id = item.getAttribute('id') || false,
content = qs(item, "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
};
}
};
/**
* Process pageList items
* @private
* @param {array} pageList
*/
process(pageList){
pageList.forEach(function(item){
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
}
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length-1]);
this.totalPages = this.lastPage - this.firstPage;
};
/**
* Replace HREFs with CFI
* TODO: implement getting CFI from Href
*/
addCFIs() {
this.pageList.forEach(function(pg){
if(!pg.cfi) {
// epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
// pg.cfi = cfi;
// pg.packageUrl = book.settings.packageUrl;
// });
}
});
}
/*
EPUBJS.generateCfiFromHref(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;
};
*/
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {string} page
*/
pageFromCfi(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 = 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 = 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;
};
/**
* Get an EpubCFI from a Page List Item
* @param {string} pg
* @return {string} cfi
*/
cfiFromPage(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;
};
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {string} page
*/
pageFromPercentage(percent){
var pg = Math.round(this.totalPages * percent);
return pg;
};
/**
* Returns a value between 0 - 1 corresponding to the location of a page
* @param {int} pg the page
* @return {number} percentage
*/
percentageFromPage(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
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
percentageFromCfi(cfi){
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
};
}
/*
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;
};
*/
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {string} page
*/
PageList.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 = 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;
};
/**
* Get an EpubCFI from a Page List Item
* @param {string} pg
* @return {string} cfi
*/
PageList.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;
};
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {string} page
*/
PageList.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
* @param {int} pg the page
* @return {number} percentage
*/
PageList.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
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
PageList.prototype.percentageFromCfi = function(cfi){
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
};
module.exports = PageList;
export default PageList;

View file

@ -1,198 +1,201 @@
var core = require('./core');
import {defer, requestAnimationFrame} from './utils/core';
/**
* Queue for handling tasks one at a time
* @class
* @param {scope} context what this will resolve to in the tasks
*/
function Queue(context){
this._q = [];
this.context = context;
this.tick = core.requestAnimationFrame;
this.running = false;
this.paused = false;
};
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}
*/
Queue.prototype.enqueue = function() {
var deferred, promise;
var queued;
var task = [].shift.call(arguments);
var args = arguments;
/**
* 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) {
return console.error("No Task Provided");
}
// Handle single args without context
// if(args && !Array.isArray(args)) {
// args = [args];
// }
if(!task) {
return console.error("No Task Provided");
}
if(typeof task === "function"){
if(typeof task === "function"){
deferred = new core.defer();
promise = deferred.promise;
deferred = new defer();
promise = deferred.promise;
queued = {
"task" : task,
"args" : args,
//"context" : context,
"deferred" : deferred,
"promise" : promise
};
queued = {
"task" : task,
"args" : args,
//"context" : context,
"deferred" : deferred,
"promise" : promise
};
} else {
// Task is a promise
queued = {
"promise" : task
};
} else {
// Task is a promise
queued = {
"promise" : task
};
}
}
this._q.push(queued);
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();
}
// 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;
};
return queued.promise;
};
/**
* Run one item
* @return {Promise}
*/
Queue.prototype.dequeue = function(){
var inwait, task, result;
/**
* Run one item
* @return {Promise}
*/
dequeue(){
var inwait, task, result;
if(this._q.length) {
inwait = this._q.shift();
task = inwait.task;
if(task){
// console.log(task)
if(this._q.length) {
inwait = this._q.shift();
task = inwait.task;
if(task){
// console.log(task)
result = task.apply(this.context, inwait.args);
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);
}, function(reason) {
inwait.deferred.reject.apply(this.context, arguments);
}.bind(this));
} else {
// Task resolves immediately
inwait.deferred.resolve.apply(this.context, result);
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(reason) {
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 if(inwait.promise) {
// Task is a promise
} else {
inwait = new defer();
inwait.deferred.resolve();
return inwait.promise;
}
} else {
inwait = new core.defer();
inwait.deferred.resolve();
return inwait.promise;
}
};
};
// Run All Immediately
dump(){
while(this._q.length) {
this.dequeue();
}
};
// Run All Immediately
Queue.prototype.dump = function(){
while(this._q.length) {
this.dequeue();
}
};
/**
* Run all tasks sequentially, at convince
* @return {Promise}
*/
run(){
/**
* Run all tasks sequentially, at convince
* @return {Promise}
*/
Queue.prototype.run = function(){
if(!this.running){
this.running = true;
this.defered = new core.defer();
}
this.tick.call(window, function() {
if(this._q.length) {
this.dequeue()
.then(function(){
this.run();
}.bind(this));
} else {
this.defered.resolve();
this.running = undefined;
if(!this.running){
this.running = true;
this.defered = new defer();
}
}.bind(this));
this.tick.call(window, function() {
// Unpause
if(this.paused == true) {
this.paused = false;
}
if(this._q.length) {
return this.defered.promise;
};
this.dequeue()
.then(function(){
this.run();
}.bind(this));
/**
* Flush all, as quickly as possible
* @return {Promise}
*/
Queue.prototype.flush = function(){
if(this.running){
return this.running;
}
if(this._q.length) {
this.running = this.dequeue()
.then(function(){
} else {
this.defered.resolve();
this.running = undefined;
return this.flush();
}.bind(this));
}
return this.running;
}
}.bind(this));
};
// Unpause
if(this.paused == true) {
this.paused = false;
}
/**
* Clear all items in wait
*/
Queue.prototype.clear = function(){
this._q = [];
this.running = false;
};
return this.defered.promise;
};
/**
* Get the number of tasks in the queue
* @return {int} tasks
*/
Queue.prototype.length = function(){
return this._q.length;
};
/**
* 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 = [];
this.running = false;
};
/**
* Get the number of tasks in the queue
* @return {int} tasks
*/
length(){
return this._q.length;
};
/**
* Pause a running queue
*/
pause(){
this.paused = true;
};
}
/**
* Pause a running queue
*/
Queue.prototype.pause = function(){
this.paused = true;
};
/**
* Create a new task from a callback
@ -203,25 +206,28 @@ Queue.prototype.pause = function(){
* @param {scope} context
* @return {function} task
*/
function Task(task, args, context){
class Task {
constructor(task, args, context){
return function(){
var toApply = arguments || [];
return function(){
var toApply = arguments || [];
return new Promise(function(resolve, reject) {
var callback = function(value){
resolve(value);
};
// Add the callback to the arguments list
toApply.push(callback);
return new Promise(function(resolve, reject) {
var callback = function(value){
resolve(value);
};
// Add the callback to the arguments list
toApply.push(callback);
// Apply all arguments to the functions
task.apply(this, toApply);
// Apply all arguments to the functions
task.apply(this, toApply);
}.bind(this));
}.bind(this));
};
};
}
};
module.exports = Queue;
export default Queue;

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
// var URI = require('urijs');
var core = require('./core');
var Url = require('./core').Url;
// import URI from 'urijs';
import { qs } from './utils/core';
import Url from './utils/url';
function base(doc, section){
export function replaceBase(doc, section){
var base;
var head;
@ -12,8 +12,8 @@ function base(doc, section){
// head = doc.querySelector("head");
// base = head.querySelector("base");
head = core.qs(doc, "head");
base = core.qs(head, "base");
head = qs(doc, "head");
base = qs(head, "base");
if(!base) {
base = doc.createElement("base");
@ -23,7 +23,7 @@ function base(doc, section){
base.setAttribute("href", section.url);
}
function canonical(doc, section){
export function replaceCanonical(doc, section){
var head;
var link;
var url = section.url; // window.location.origin + window.location.pathname + "?loc=" + encodeURIComponent(section.url);
@ -32,8 +32,8 @@ function canonical(doc, section){
return;
}
head = core.qs(doc, "head");
link = core.qs(head, "link[rel='canonical']");
head = qs(doc, "head");
link = qs(head, "link[rel='canonical']");
if (link) {
link.setAttribute("href", url);
@ -45,10 +45,10 @@ function canonical(doc, section){
}
}
function links(view, renderer) {
export function replaceLinks(view, renderer) {
var links = view.document.querySelectorAll("a[href]");
var replaceLinks = function(link){
var replaceLink = function(link){
var href = link.getAttribute("href");
if(href.indexOf("mailto:") === 0){
@ -92,13 +92,13 @@ function links(view, renderer) {
}.bind(this);
for (var i = 0; i < links.length; i++) {
replaceLinks(links[i]);
replaceLink(links[i]);
}
};
function substitute(content, urls, replacements) {
export function substitute(content, urls, replacements) {
urls.forEach(function(url, i){
if (url && replacements[i]) {
content = content.replace(new RegExp(url, 'g'), replacements[i]);
@ -106,9 +106,3 @@ function substitute(content, urls, replacements) {
});
return content;
}
module.exports = {
'base': base,
'canonical' : canonical,
'links': links,
'substitute': substitute
};

View file

@ -1,12 +1,12 @@
var core = require('./core');
var Path = require('./core').Path;
import {defer, isXml, parse} from './utils/core';
import Path from './utils/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 uri;
var deferred = new core.defer();
var deferred = new defer();
var xhr = new XMLHttpRequest();
@ -49,7 +49,7 @@ function request(url, type, withCredentials, headers) {
}
if(core.isXml(type)) {
if(isXml(type)) {
// xhr.responseType = "document";
xhr.overrideMimeType('text/xml'); // for OPF parsing
}
@ -106,16 +106,16 @@ function request(url, type, withCredentials, headers) {
if(responseXML){
r = this.responseXML;
} else
if(core.isXml(type)){
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 = core.parse(this.response, "text/xml");
r = parse(this.response, "text/xml");
}else
if(type == 'xhtml'){
r = core.parse(this.response, "application/xhtml+xml");
r = parse(this.response, "application/xhtml+xml");
}else
if(type == 'html' || type == 'htm'){
r = core.parse(this.response, "text/html");
r = parse(this.response, "text/html");
}else
if(type == 'json'){
r = JSON.parse(this.response);
@ -149,4 +149,4 @@ function request(url, type, withCredentials, headers) {
return deferred.promise;
};
module.exports = request;
export default request;

View file

@ -1,7 +1,7 @@
var replace = require('./replacements');
var core = require('./core');
var Path = require('./core').Path;
var path = require('path');
import {substitute} from './replacements';
import {createBase64Url, createBlobUrl} from './utils/core';
import Path from './utils/path';
import path from 'path-webpack';
/**
* Handle Package Resources
@ -12,234 +12,235 @@ var path = require('path');
* @param {[Archive]} options.archive
* @param {[method]} options.resolver
*/
function Resources(manifest, options) {
this.settings = {
replacements: (options && options.replacements) || 'base64',
archive: (options && options.archive),
resolver: (options && options.resolver)
};
this.manifest = manifest;
this.resources = Object.keys(manifest).
map(function (key){
return manifest[key];
});
class Resources {
constructor(manifest, options) {
this.settings = {
replacements: (options && options.replacements) || 'base64',
archive: (options && options.archive),
resolver: (options && options.resolver)
};
this.manifest = manifest;
this.resources = Object.keys(manifest).
map(function (key){
return manifest[key];
});
this.replacementUrls = [];
this.replacementUrls = [];
this.split();
this.splitUrls();
}
/**
* Split resources by type
* @private
*/
Resources.prototype.split = function(){
// HTML
this.html = this.resources.
filter(function (item){
if (item.type === "application/xhtml+xml" ||
item.type === "text/html") {
return true;
}
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
};
/**
* Convert split resources into Urls
* @private
*/
Resources.prototype.splitUrls = function(){
// All Assets Urls
this.urls = this.assets.
map(function(item) {
return item.href;
}.bind(this));
// Css Urls
this.cssUrls = this.css.map(function(item) {
return item.href;
});
};
/**
* Create blob urls for all the assets
* @param {Archive} archive
* @param {resolver} resolver Url resolver
* @return {Promise} returns replacement urls
*/
Resources.prototype.replacements = function(archive, resolver){
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (this.settings.replacements === "none") {
return new Promise(function(resolve, reject) {
resolve(this.urls);
}.bind(this));
this.split();
this.splitUrls();
}
var replacements = this.urls.
map(function(url) {
var absolute = resolver(url);
/**
* Split resources by type
* @private
*/
split(){
return archive.createUrl(absolute, {"base64": (this.settings.replacements === "base64")});
}.bind(this))
return Promise.all(replacements)
.then(function(replacementUrls) {
this.replacementUrls = replacementUrls;
return replacementUrls;
}.bind(this));
};
/**
* Replace URLs in CSS resources
* @private
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise}
*/
Resources.prototype.replaceCss = function(archive, resolver){
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
var replacement = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
var indexInUrls = this.urls.indexOf(href);
if (indexInUrls > -1) {
this.replacementUrls[indexInUrls] = replacementUrl;
// HTML
this.html = this.resources.
filter(function (item){
if (item.type === "application/xhtml+xml" ||
item.type === "text/html") {
return true;
}
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
};
/**
* Convert split resources into Urls
* @private
*/
splitUrls(){
// All Assets Urls
this.urls = this.assets.
map(function(item) {
return item.href;
}.bind(this));
replaced.push(replacement);
}.bind(this));
return Promise.all(replaced);
};
// Css Urls
this.cssUrls = this.css.map(function(item) {
return item.href;
});
/**
* Create a new CSS file with the replaced URLs
* @private
* @param {string} href the original css file
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise} returns a BlobUrl to the new CSS file or a data url
*/
Resources.prototype.createCssFile = function(href, archive, resolver){
var newUrl;
var indexInUrls;
};
/**
* Create blob urls for all the assets
* @param {Archive} archive
* @param {resolver} resolver Url resolver
* @return {Promise} returns replacement urls
*/
replacements(archive, resolver){
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (path.isAbsolute(href)) {
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
if (this.settings.replacements === "none") {
return new Promise(function(resolve, reject) {
resolve(this.urls);
}.bind(this));
}
var absolute = resolver(href);
var replacements = this.urls.
map(function(url) {
var absolute = resolver(url);
// Get the text of the css file from the archive
var textResponse = archive.getText(absolute);
// Get asset links relative to css file
var relUrls = this.urls.map(function(assetHref) {
var resolved = resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
return archive.createUrl(absolute, {"base64": (this.settings.replacements === "base64")});
}.bind(this))
return relative;
return Promise.all(replacements)
.then(function(replacementUrls) {
this.replacementUrls = replacementUrls;
return replacementUrls;
}.bind(this));
};
return textResponse.then(function (text) {
// Replacements in the css text
text = replace.substitute(text, relUrls, this.replacementUrls);
/**
* Replace URLs in CSS resources
* @private
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise}
*/
replaceCss(archive, resolver){
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
var replacement = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
var indexInUrls = this.urls.indexOf(href);
if (indexInUrls > -1) {
this.replacementUrls[indexInUrls] = replacementUrl;
}
}.bind(this));
// Get the new url
if (this.settings.replacements === "base64") {
newUrl = core.createBase64Url(text, 'text/css');
} else {
newUrl = core.createBlobUrl(text, 'text/css');
replaced.push(replacement);
}.bind(this));
return Promise.all(replaced);
};
/**
* Create a new CSS file with the replaced URLs
* @private
* @param {string} href the original css file
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise} returns a BlobUrl to the new CSS file or a data url
*/
createCssFile(href, archive, resolver){
var newUrl;
var indexInUrls;
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (path.isAbsolute(href)) {
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
}
return newUrl;
}.bind(this));
var absolute = resolver(href);
};
// Get the text of the css file from the archive
var textResponse = archive.getText(absolute);
// Get asset links relative to css file
var relUrls = this.urls.map(function(assetHref) {
var resolved = resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
/**
* Resolve all resources URLs relative to an absolute URL
* @param {string} absolute to be resolved to
* @param {[resolver]} resolver
* @return {string[]} array with relative Urls
*/
Resources.prototype.relativeTo = function(absolute, resolver){
resolver = resolver || this.settings.resolver;
return relative;
}.bind(this));
// Get Urls relative to current sections
return this.urls.
map(function(href) {
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
};
return textResponse.then(function (text) {
// Replacements in the css text
text = substitute(text, relUrls, this.replacementUrls);
/**
* Get a URL for a resource
* @param {string} path
* @return {string} url
*/
Resources.prototype.get = function(path) {
var indexInUrls = this.urls.indexOf(path);
if (indexInUrls === -1) {
return;
}
if (this.replacementUrls.length) {
return new Promise(function(resolve, reject) {
resolve(this.replacementUrls[indexInUrls]);
}.bind(this));
} else {
return archive.createUrl(absolute,
{"base64": (this.settings.replacements === "base64")})
// Get the new url
if (this.settings.replacements === "base64") {
newUrl = createBase64Url(text, 'text/css');
} else {
newUrl = createBlobUrl(text, 'text/css');
}
return newUrl;
}.bind(this));
};
/**
* Resolve all resources URLs relative to an absolute URL
* @param {string} absolute to be resolved to
* @param {[resolver]} resolver
* @return {string[]} array with relative Urls
*/
relativeTo(absolute, resolver){
resolver = resolver || this.settings.resolver;
// Get Urls relative to current sections
return this.urls.
map(function(href) {
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
};
/**
* Get a URL for a resource
* @param {string} path
* @return {string} url
*/
get(path) {
var indexInUrls = this.urls.indexOf(path);
if (indexInUrls === -1) {
return;
}
if (this.replacementUrls.length) {
return new Promise(function(resolve, reject) {
resolve(this.replacementUrls[indexInUrls]);
}.bind(this));
} else {
return archive.createUrl(absolute,
{"base64": (this.settings.replacements === "base64")})
}
}
/**
* Substitute urls in content, with replacements,
* relative to a url if provided
* @param {string} content
* @param {[string]} url url to resolve to
* @return {string} content with urls substituted
*/
substitute(content, url) {
var relUrls;
if (url) {
relUrls = this.relativeTo(url);
} else {
relUrls = this.urls;
}
return substitute(content, relUrls, this.replacementUrls);
};
}
/**
* Substitute urls in content, with replacements,
* relative to a url if provided
* @param {string} content
* @param {[string]} url url to resolve to
* @return {string} content with urls substituted
*/
Resources.prototype.substitute = function(content, url) {
var relUrls;
if (url) {
relUrls = this.relativeTo(url);
} else {
relUrls = this.urls;
}
return replace.substitute(content, relUrls, this.replacementUrls);
};
module.exports = Resources;
export default Resources;

View file

@ -1,7 +1,7 @@
var core = require('./core');
var EpubCFI = require('./epubcfi');
var Hook = require('./hook');
var Url = require('./core').Url;
import { defer } from './utils/core';
import EpubCFI from './epubcfi';
import Hook from './hook';
import Url from './utils/url';
/**
* Represents a Section of the Book
@ -9,178 +9,180 @@ var Url = require('./core').Url;
* @param {object} item The spine item representing the section
* @param {object} hooks hooks for serialize and content
*/
function Section(item, hooks){
this.idref = item.idref;
this.linear = item.linear;
this.properties = item.properties;
this.index = item.index;
this.href = item.href;
this.url = item.url;
this.next = item.next;
this.prev = item.prev;
class Section {
constructor(item, hooks){
this.idref = item.idref;
this.linear = item.linear;
this.properties = item.properties;
this.index = item.index;
this.href = item.href;
this.url = item.url;
this.next = item.next;
this.prev = item.prev;
this.cfiBase = item.cfiBase;
this.cfiBase = item.cfiBase;
if (hooks) {
this.hooks = hooks;
} else {
this.hooks = {};
this.hooks.serialize = new Hook(this);
this.hooks.content = new Hook(this);
}
};
/**
* Load the section from its url
* @param {method} _request a request method to use for loading
* @return {document} a promise with the xml document
*/
Section.prototype.load = function(_request){
var request = _request || this.request || require('./request');
var loading = new core.defer();
var loaded = loading.promise;
if(this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(function(xml){
var base;
var directory = new Url(this.url).directory;
this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
loading.reject(error);
});
}
return loaded;
};
/**
* Adds a base tag for resolving urls in the section
* @private
* @param {document} _document
*/
Section.prototype.base = function(_document){
var task = new core.defer();
var base = _document.createElement("base"); // TODO: check if exists
var head;
base.setAttribute("href", window.location.origin + "/" +this.url);
if(_document) {
head = _document.querySelector("head");
}
if(head) {
head.insertBefore(base, head.firstChild);
task.resolve();
} else {
task.reject(new Error("No head to insert into"));
}
return task.promise;
};
/**
* Render the contents of a section
* @param {method} _request a request method to use for loading
* @return {string} output a serialized XML Document
*/
Section.prototype.render = function(_request){
var rendering = new core.defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
this.load(_request).
then(function(contents){
var serializer;
if (typeof XMLSerializer === "undefined") {
XMLSerializer = require('xmldom').XMLSerializer;
if (hooks) {
this.hooks = hooks;
} else {
this.hooks = {};
this.hooks.serialize = new Hook(this);
this.hooks.content = new Hook(this);
}
serializer = new XMLSerializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
rendering.reject(error);
});
return rendered;
};
/**
* Find a string in a section
* TODO: need reimplementation from v0.2
* @param {string} query [description]
* @return {[type]} [description]
*/
Section.prototype.find = function(query){
};
/**
* Reconciles the current chapters layout properies with
* the global layout properities.
* @param {object} global The globa layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
Section.prototype.reconcileLayoutSettings = function(global){
//-- Get the global defaults
var settings = {
layout : global.layout,
spread : global.spread,
orientation : global.orientation
};
//-- Get the chapter's display type
this.properties.forEach(function(prop){
var rendition = prop.replace("rendition:", '');
var split = rendition.indexOf("-");
var property, value;
/**
* Load the section from its url
* @param {method} _request a request method to use for loading
* @return {document} a promise with the xml document
*/
load(_request){
var request = _request || this.request || require('./request');
var loading = new defer();
var loaded = loading.promise;
if(split != -1){
property = rendition.slice(0, split);
value = rendition.slice(split+1);
if(this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(function(xml){
var base;
var directory = new Url(this.url).directory;
settings[property] = value;
this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
loading.reject(error);
});
}
});
return settings;
};
/**
* Get a CFI from a Range in the Section
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
Section.prototype.cfiFromRange = function(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
};
return loaded;
};
/**
* Get a CFI from an Element in the Section
* @param {element} el
* @return {string} cfi an EpubCFI string
*/
Section.prototype.cfiFromElement = function(el) {
return new EpubCFI(el, this.cfiBase).toString();
};
/**
* Adds a base tag for resolving urls in the section
* @private
* @param {document} _document
*/
base(_document){
var task = new defer();
var base = _document.createElement("base"); // TODO: check if exists
var head;
module.exports = Section;
base.setAttribute("href", window.location.origin + "/" +this.url);
if(_document) {
head = _document.querySelector("head");
}
if(head) {
head.insertBefore(base, head.firstChild);
task.resolve();
} else {
task.reject(new Error("No head to insert into"));
}
return task.promise;
};
/**
* Render the contents of a section
* @param {method} _request a request method to use for loading
* @return {string} output a serialized XML Document
*/
render(_request){
var rendering = new defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
this.load(_request).
then(function(contents){
var serializer;
if (typeof XMLSerializer === "undefined") {
XMLSerializer = require('xmldom').XMLSerializer;
}
serializer = new XMLSerializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
rendering.reject(error);
});
return rendered;
};
/**
* Find a string in a section
* TODO: need reimplementation from v0.2
* @param {string} query [description]
* @return {[type]} [description]
*/
find(query){
};
/**
* Reconciles the current chapters layout properies with
* the global layout properities.
* @param {object} global The globa layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
reconcileLayoutSettings(global){
//-- Get the global defaults
var settings = {
layout : global.layout,
spread : global.spread,
orientation : global.orientation
};
//-- Get the chapter's display type
this.properties.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;
};
/**
* Get a CFI from a Range in the Section
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
cfiFromRange(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
};
/**
* Get a CFI from an Element in the Section
* @param {element} el
* @return {string} cfi an EpubCFI string
*/
cfiFromElement(el) {
return new EpubCFI(el, this.cfiBase).toString();
};
}
export default Section;

View file

@ -1,161 +1,163 @@
var core = require('./core');
var EpubCFI = require('./epubcfi');
var Hook = require('./hook');
var Section = require('./section');
var replacements = require('./replacements');
import core from './utils/core';
import EpubCFI from './epubcfi';
import Hook from './hook';
import Section from './section';
import {replaceBase, replaceCanonical} from './replacements';
/**
* A collection of Spine Items
*/
function Spine(){
this.spineItems = [];
this.spineByHref = {};
this.spineById = {};
class Spine {
constructor() {
this.spineItems = [];
this.spineByHref = {};
this.spineById = {};
this.hooks = {};
this.hooks.serialize = new Hook();
this.hooks.content = new Hook();
this.hooks = {};
this.hooks.serialize = new Hook();
this.hooks.content = new Hook();
// Register replacements
this.hooks.content.register(replacements.base);
this.hooks.content.register(replacements.canonical);
// Register replacements
this.hooks.content.register(replaceBase);
this.hooks.content.register(replaceCanonical);
this.epubcfi = new EpubCFI();
this.epubcfi = new EpubCFI();
this.loaded = false;
};
this.loaded = false;
};
/**
* Unpack items from a opf into spine items
* @param {Package} _package
* @param {method} resolver URL resolver
*/
Spine.prototype.unpack = function(_package, resolver) {
/**
* Unpack items from a opf into spine items
* @param {Package} _package
* @param {method} resolver URL resolver
*/
unpack(_package, resolver) {
this.items = _package.spine;
this.manifest = _package.manifest;
this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || '';
this.length = this.items.length;
this.items = _package.spine;
this.manifest = _package.manifest;
this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || '';
this.length = this.items.length;
this.items.forEach(function(item, index){
var href, url;
var manifestItem = this.manifest[item.idref];
var spineItem;
this.items.forEach(function(item, index){
var href, url;
var manifestItem = this.manifest[item.idref];
var spineItem;
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref);
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref);
if(manifestItem) {
item.href = manifestItem.href;
item.url = resolver(item.href, true);
if(manifestItem) {
item.href = manifestItem.href;
item.url = resolver(item.href, true);
if(manifestItem.properties.length){
item.properties.push.apply(item.properties, manifestItem.properties);
if(manifestItem.properties.length){
item.properties.push.apply(item.properties, manifestItem.properties);
}
}
item.prev = function(){ return this.get(index-1); }.bind(this);
item.next = function(){ return this.get(index+1); }.bind(this);
spineItem = new Section(item, this.hooks);
this.append(spineItem);
}.bind(this));
this.loaded = true;
};
/**
* Get an item from the spine
* @param {[string|int]} target
* @return {Section} section
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
get(target) {
var index = 0;
if(this.epubcfi.isCfiString(target)) {
cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if(target && (typeof target === "number" || isNaN(target) === false)){
index = target;
} else if(target && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(target) {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target];
}
item.prev = function(){ return this.get(index-1); }.bind(this);
item.next = function(){ return this.get(index+1); }.bind(this);
return this.spineItems[index] || null;
};
spineItem = new Section(item, this.hooks);
/**
* Append a Section to the Spine
* @private
* @param {Section} section
*/
append(section) {
var index = this.spineItems.length;
section.index = index;
this.append(spineItem);
this.spineItems.push(section);
this.spineByHref[section.href] = index;
this.spineById[section.idref] = index;
}.bind(this));
return index;
};
this.loaded = true;
};
/**
* Prepend a Section to the Spine
* @private
* @param {Section} section
*/
prepend(section) {
var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
/**
* Get an item from the spine
* @param {[string|int]} target
* @return {Section} section
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
Spine.prototype.get = function(target) {
var index = 0;
// Re-index
this.spineItems.forEach(function(item, index){
item.index = index;
});
if(this.epubcfi.isCfiString(target)) {
cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if(target && (typeof target === "number" || isNaN(target) === false)){
index = target;
} else if(target && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(target) {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target];
}
return 0;
};
return this.spineItems[index] || null;
};
// insert(section, index) {
//
// };
/**
* Append a Section to the Spine
* @private
* @param {Section} section
*/
Spine.prototype.append = function(section) {
var index = this.spineItems.length;
section.index = index;
/**
* Remove a Section from the Spine
* @private
* @param {Section} section
*/
remove(section) {
var index = this.spineItems.indexOf(section);
this.spineItems.push(section);
if(index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
this.spineByHref[section.href] = index;
this.spineById[section.idref] = index;
return this.spineItems.splice(index, 1);
}
};
return index;
};
/**
* Loop over the Sections in the Spine
* @return {method} forEach
*/
each() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
};
}
/**
* Prepend a Section to the Spine
* @private
* @param {Section} section
*/
Spine.prototype.prepend = function(section) {
var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
// Re-index
this.spineItems.forEach(function(item, index){
item.index = index;
});
return 0;
};
// Spine.prototype.insert = function(section, index) {
//
// };
/**
* Remove a Section from the Spine
* @private
* @param {Section} section
*/
Spine.prototype.remove = function(section) {
var index = this.spineItems.indexOf(section);
if(index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
return this.spineItems.splice(index, 1);
}
};
/**
* Loop over the Sections in the Spine
* @return {method} forEach
*/
Spine.prototype.each = function() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
};
module.exports = Spine;
export default Spine;

View file

@ -1,155 +1,158 @@
var Url = require('./core').Url;
import Url from './utils/url';
function Themes(rendition) {
this.rendition = rendition;
this._themes = {
"default" : {
"rules" : [],
"url" : "",
"serialized" : ''
class Themes {
constructor(rendition) {
this.rendition = rendition;
this._themes = {
"default" : {
"rules" : [],
"url" : "",
"serialized" : ''
}
};
this._overrides = {};
this._current = "default";
this._injected = [];
this.rendition.hooks.content.register(this.inject.bind(this));
this.rendition.hooks.content.register(this.overrides.bind(this));
}
register () {
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof(arguments[0]) === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof(arguments[0]) === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "object") {
return this.registerRules(arguments[0], arguments[1]);
}
};
this._overrides = {};
this._current = "default";
this._injected = [];
this.rendition.hooks.content.register(this.inject.bind(this));
this.rendition.hooks.content.register(this.overrides.bind(this));
default (theme) {
if (!theme) {
return;
}
if (typeof(theme) === "string") {
return this.registerUrl("default", theme);
}
if (typeof(theme) === "object") {
return this.registerRules("default", theme);
}
};
registerThemes (themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof(themes[theme]) === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
};
registerUrl (name, input) {
var url = new Url(input);
this._themes[name] = { "url": url.toString() };
};
registerRules (name, rules) {
this._themes[name] = { "rules": rules };
// TODO: serialize css rules
};
apply (name) {
var prev = this._current;
var contents;
this._current = name;
this.update(name);
contents = this.rendition.getContents();
contents.forEach(function (content) {
content.removeClass(prev);
content.addClass(name);
}.bind(this));
};
update (name) {
var contents = this.rendition.getContents();
contents.forEach(function (content) {
this.add(name, content);
}.bind(this));
};
inject (view) {
var links = [];
var themes = this._themes;
var theme;
for (var name in themes) {
if (themes.hasOwnProperty(name)) {
theme = themes[name];
if(theme.rules || (theme.url && links.indexOf(theme.url) === -1)) {
this.add(name, view.contents);
}
}
}
if(this._current) {
view.contents.addClass(this._current);
}
};
add (name, contents) {
var theme = this._themes[name];
if (!theme) {
return;
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
// TODO: handle serialized
} else if (theme.rules && theme.rules.length) {
contents.addStylesheetRules(theme.rules);
theme.injected = true;
}
};
override (name, value) {
var contents = this.rendition.getContents();
this._overrides[name] = value;
contents.forEach(function (content) {
content.css(name, this._overrides[name]);
}.bind(this));
};
overrides (view) {
var contents = view.contents;
var overrides = this._overrides;
var rules = [];
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule]);
}
}
};
fontSize (size) {
this.override("font-size", size);
};
}
Themes.prototype.register = function () {
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof(arguments[0]) === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof(arguments[0]) === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "object") {
return this.registerRules(arguments[0], arguments[1]);
}
};
Themes.prototype.default = function (theme) {
if (!theme) {
return;
}
if (typeof(theme) === "string") {
return this.registerUrl("default", theme);
}
if (typeof(theme) === "object") {
return this.registerRules("default", theme);
}
};
Themes.prototype.registerThemes = function (themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof(themes[theme]) === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
};
Themes.prototype.registerUrl = function (name, input) {
var url = new Url(input);
this._themes[name] = { "url": url.toString() };
};
Themes.prototype.registerRules = function (name, rules) {
this._themes[name] = { "rules": rules };
// TODO: serialize css rules
};
Themes.prototype.apply = function (name) {
var prev = this._current;
var contents;
this._current = name;
this.update(name);
contents = this.rendition.getContents();
contents.forEach(function (content) {
content.removeClass(prev);
content.addClass(name);
}.bind(this));
};
Themes.prototype.update = function (name) {
var contents = this.rendition.getContents();
contents.forEach(function (content) {
this.add(name, content);
}.bind(this));
};
Themes.prototype.inject = function (view) {
var links = [];
var themes = this._themes;
var theme;
for (var name in themes) {
if (themes.hasOwnProperty(name)) {
theme = themes[name];
if(theme.rules || (theme.url && links.indexOf(theme.url) === -1)) {
this.add(name, view.contents);
}
}
}
if(this._current) {
view.contents.addClass(this._current);
}
};
Themes.prototype.add = function (name, contents) {
var theme = this._themes[name];
if (!theme) {
return;
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
// TODO: handle serialized
} else if (theme.rules && theme.rules.length) {
contents.addStylesheetRules(theme.rules);
theme.injected = true;
}
};
Themes.prototype.override = function (name, value) {
var contents = this.rendition.getContents();
this._overrides[name] = value;
contents.forEach(function (content) {
content.css(name, this._overrides[name]);
}.bind(this));
};
Themes.prototype.overrides = function (view) {
var contents = view.contents;
var overrides = this._overrides;
var rules = [];
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule]);
}
}
};
Themes.prototype.fontSize = function (size) {
this.override("font-size", size);
};
module.exports = Themes;
export default Themes;

View file

@ -1,144 +1,11 @@
var base64 = require('base64-js');
var path = require('path');
export const requestAnimationFrame = (typeof window != 'undefined') ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
var requestAnimationFrame = (typeof window != 'undefined') ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
/**
* creates a uri object
* @param {string} urlString a url string (relative or absolute)
* @param {[string]} baseString optional base for the url,
* default to window.location.href
* @return {object} url
*/
function Url(urlString, baseString) {
var absolute = (urlString.indexOf('://') > -1);
var pathname = urlString;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.fragment = "";
this.search = "";
this.base = baseString;
if (!absolute && (typeof(baseString) !== "string")) {
this.base = window && window.location.href;
}
// URL Polyfill doesn't throw an error if base is empty
if (absolute || this.base) {
try {
this.Url = new URL(urlString, this.base);
this.href = this.Url.href;
this.protocol = this.Url.protocol;
this.origin = this.Url.origin;
this.fragment = this.Url.fragment;
this.search = this.Url.search;
pathname = this.Url.pathname;
} catch (e) {
// Skip URL parsing
this.Url = undefined;
}
}
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
}
Url.prototype.path = function () {
return this.Path;
};
Url.prototype.resolve = function (what) {
var isAbsolute = (what.indexOf('://') > -1);
var fullpath;
if (isAbsolute) {
return what;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
};
Url.prototype.relative = function (what) {
return path.relative(what, this.directory);
};
Url.prototype.toString = function () {
return this.href;
};
function Path(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);
}
Path.prototype.parse = function (what) {
return path.parse(what);
};
Path.prototype.isAbsolute = function (what) {
return path.isAbsolute(what || this.path);
};
Path.prototype.isDirectory = function (what) {
return (what.charAt(what.length-1) === '/');
};
Path.prototype.resolve = function (what) {
return path.resolve(this.directory, what);
};
Path.prototype.relative = function (what) {
return path.relative(this.directory, what);
};
Path.prototype.splitPath = function(filename) {
return this.splitPathRe.exec(filename).slice(1);
};
Path.prototype.toString = function () {
return this.path;
};
function assertPath(path) {
if (typeof path !== 'string') {
throw new TypeError('Path must be a string. Received ', path);
}
};
function isElement(obj) {
export function isElement(obj) {
return !!(obj && obj.nodeType == 1);
};
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function uuid() {
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;
@ -149,7 +16,7 @@ function uuid() {
};
// From Lodash
function values(object) {
export function values(object) {
var index = -1,
props = Object.keys(object),
length = props.length,
@ -161,70 +28,7 @@ function values(object) {
return result;
};
function resolveUrl(base, path) {
var url = [],
segments = [],
baseUri = uri(base),
pathUri = uri(path),
baseDirectory = baseUri.directory,
pathDirectory = pathUri.directory,
directories = [],
// folders = base.split("/"),
paths;
// if(uri.host) {
// return path;
// }
if(baseDirectory[0] === "/") {
baseDirectory = baseDirectory.substring(1);
}
if(pathDirectory[pathDirectory.length-1] === "/") {
baseDirectory = baseDirectory.substring(0, baseDirectory.length-1);
}
if(pathDirectory[0] === "/") {
pathDirectory = pathDirectory.substring(1);
}
if(pathDirectory[pathDirectory.length-1] === "/") {
pathDirectory = pathDirectory.substring(0, pathDirectory.length-1);
}
if(baseDirectory) {
directories = baseDirectory.split("/");
}
paths = pathDirectory.split("/");
paths.reverse().forEach(function(part, index){
if(part === ".."){
directories.pop();
} else if(part === directories[directories.length-1]) {
directories.pop();
segments.unshift(part);
} else {
segments.unshift(part);
}
});
url = [baseUri.origin];
if(directories.length) {
url = url.concat(directories);
}
if(segments) {
url = url.concat(segments);
}
url = url.concat(pathUri.filename);
return url.join("/");
};
function documentHeight() {
export function documentHeight() {
return Math.max(
document.documentElement.clientHeight,
document.body.scrollHeight,
@ -234,15 +38,15 @@ function documentHeight() {
);
};
function isNumber(n) {
export function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
function isFloat(n) {
export function isFloat(n) {
return isNumber(n) && (Math.floor(n) !== n);
}
function prefixed(unprefixed) {
export function prefixed(unprefixed) {
var vendors = ["Webkit", "Moz", "O", "ms" ],
prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
@ -261,7 +65,7 @@ function prefixed(unprefixed) {
return unprefixed;
};
function defaults(obj) {
export function defaults(obj) {
for (var i = 1, length = arguments.length; i < length; i++) {
var source = arguments[i];
for (var prop in source) {
@ -271,7 +75,7 @@ function defaults(obj) {
return obj;
};
function extend(target) {
export function extend(target) {
var sources = [].slice.call(arguments, 1);
sources.forEach(function (source) {
if(!source) return;
@ -284,14 +88,14 @@ function extend(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
function insert(item, array, compareFunction) {
export function insert(item, array, compareFunction) {
var location = locationOf(item, array, compareFunction);
array.splice(location, 0, item);
return location;
};
// Returns where something would fit in
function locationOf(item, array, compareFunction, _start, _end) {
export function locationOf(item, array, compareFunction, _start, _end) {
var start = _start || 0;
var end = _end || array.length;
var pivot = parseInt(start + (end - start) / 2);
@ -322,7 +126,7 @@ function locationOf(item, array, compareFunction, _start, _end) {
}
};
// Returns -1 of mpt found
function indexOfSorted(item, array, compareFunction, _start, _end) {
export function indexOfSorted(item, array, compareFunction, _start, _end) {
var start = _start || 0;
var end = _end || array.length;
var pivot = parseInt(start + (end - start) / 2);
@ -352,7 +156,7 @@ function indexOfSorted(item, array, compareFunction, _start, _end) {
}
};
function bounds(el) {
export function bounds(el) {
var style = window.getComputedStyle(el);
var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
@ -376,7 +180,7 @@ function bounds(el) {
};
function borders(el) {
export function borders(el) {
var style = window.getComputedStyle(el);
var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
@ -400,7 +204,7 @@ function borders(el) {
};
function windowBounds() {
export function windowBounds() {
var width = window.innerWidth;
var height = window.innerHeight;
@ -417,7 +221,7 @@ function windowBounds() {
};
//https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
function cleanStringForXpath(str) {
export function cleanStringForXpath(str) {
var parts = str.match(/[^'"]+|['"]/g);
parts = parts.map(function(part){
if (part === "'") {
@ -432,7 +236,7 @@ function cleanStringForXpath(str) {
return "concat(\'\'," + parts.join(",") + ")";
};
function indexOfTextNode(textNode){
export function indexOfTextNode(textNode){
var parent = textNode.parentNode;
var children = parent.childNodes;
var sib;
@ -448,17 +252,17 @@ function indexOfTextNode(textNode){
return index;
};
function isXml(ext) {
export function isXml(ext) {
return ['xml', 'opf', 'ncx'].indexOf(ext) > -1;
}
function createBlob(content, mime){
export function createBlob(content, mime){
var blob = new Blob([content], {type : mime });
return blob;
};
function createBlobUrl(content, mime){
export function createBlobUrl(content, mime){
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var blob = this.createBlob(content, mime);
@ -468,7 +272,7 @@ function createBlobUrl(content, mime){
return tempUrl;
};
function createBase64Url(content, mime){
export function createBase64Url(content, mime){
var string;
var data;
var datauri;
@ -485,11 +289,11 @@ function createBase64Url(content, mime){
return datauri;
};
function type(obj){
export function type(obj){
return Object.prototype.toString.call(obj).slice(8, -1);
}
function parse(markup, mime, forceXMLDom) {
export function parse(markup, mime, forceXMLDom) {
var doc;
if (typeof DOMParser === "undefined" || forceXMLDom) {
@ -502,7 +306,7 @@ function parse(markup, mime, forceXMLDom) {
return doc;
}
function qs(el, sel) {
export function qs(el, sel) {
var elements;
if (!el) {
throw new Error('No Element Provided');
@ -518,7 +322,7 @@ function qs(el, sel) {
}
}
function qsa(el, sel) {
export function qsa(el, sel) {
if (typeof el.querySelector != "undefined") {
return el.querySelectorAll(sel);
@ -527,7 +331,7 @@ function qsa(el, sel) {
}
}
function qsp(el, sel, props) {
export function qsp(el, sel, props) {
var q, filtered;
if (typeof el.querySelector != "undefined") {
sel += '[';
@ -558,7 +362,7 @@ function qsp(el, sel, props) {
* @param {element} root element to start with
* @param {function} func function to run on each element
*/
function sprint(root, func) {
export function sprint(root, func) {
var doc = root.ownerDocument || root;
if (typeof(doc.createTreeWalker) !== "undefined") {
treeWalker(root, func, NodeFilter.SHOW_TEXT);
@ -571,14 +375,15 @@ function sprint(root, func) {
}
}
function treeWalker(root, func, filter) {
export function treeWalker(root, func, filter) {
var treeWalker = document.createTreeWalker(root, filter, null, false);
let node;
while ((node = treeWalker.nextNode())) {
func(node);
}
}
// function walk(root, func, onlyText) {
// export function walk(root, func, onlyText) {
// var node = root;
//
// if (node && !onlyText || node.nodeType === 3) { // Node.TEXT_NODE
@ -597,7 +402,7 @@ function treeWalker(root, func, filter) {
* @param callback return false for continue,true for break
* @return boolean true: break visit;
*/
function walk(node,callback){
export function walk(node,callback){
if(callback(node)){
return true;
}
@ -608,7 +413,7 @@ function walk(node,callback){
}
}
function blob2base64(blob, cb) {
export function blob2base64(blob, cb) {
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
@ -617,7 +422,7 @@ function blob2base64(blob, cb) {
}
// From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
function defer() {
export function defer() {
/* A method to resolve the associated Promise with the value passed.
* If the promise is already settled it does nothing.
*
@ -648,14 +453,14 @@ function defer() {
Object.freeze(this);
}
function querySelectorByType(html, element, type){
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 = this.qsa(html, element);
query = qsa(html, element);
for (var i = 0; i < query.length; i++) {
if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type) {
return query[i];
@ -666,53 +471,14 @@ function defer() {
}
}
function children(el) {
var children = [];
export function findChildren(el) {
var result = [];
var childNodes = el.parentNode.childNodes;
for (var i = 0; i < childNodes.length; i++) {
node = childNodes[i];
let node = childNodes[i];
if (node.nodeType === 1) {
children.push(node);
result.push(node);
}
};
return children;
return result;
}
module.exports = {
'isElement': isElement,
'uuid': uuid,
'values': values,
'resolveUrl': resolveUrl,
'indexOfSorted': indexOfSorted,
'documentHeight': documentHeight,
'isNumber': isNumber,
'isFloat': isFloat,
'prefixed': prefixed,
'defaults': defaults,
'extend': extend,
'insert': insert,
'locationOf': locationOf,
'indexOfSorted': indexOfSorted,
'requestAnimationFrame': requestAnimationFrame,
'bounds': bounds,
'borders': borders,
'windowBounds': windowBounds,
'cleanStringForXpath': cleanStringForXpath,
'indexOfTextNode': indexOfTextNode,
'isXml': isXml,
'createBlob': createBlob,
'createBlobUrl': createBlobUrl,
'type': type,
'parse' : parse,
'qs' : qs,
'qsa' : qsa,
'qsp' : qsp,
'blob2base64' : blob2base64,
'createBase64Url': createBase64Url,
'defer': defer,
'Url': Url,
'Path': Path,
'querySelectorByType': querySelectorByType,
'sprint' : sprint,
'children' : children
};

57
src/utils/path.js Normal file
View file

@ -0,0 +1,57 @@
import path from 'path-webpack';
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 (what) {
return path.parse(what);
};
isAbsolute (what) {
return path.isAbsolute(what || this.path);
};
isDirectory (what) {
return (what.charAt(what.length-1) === '/');
};
resolve (what) {
return path.resolve(this.directory, what);
};
relative (what) {
return path.relative(this.directory, what);
};
splitPath(filename) {
return this.splitPathRe.exec(filename).slice(1);
};
toString () {
return this.path;
};
}
export default Path

83
src/utils/url.js Normal file
View file

@ -0,0 +1,83 @@
import Path from './path'
import path from 'path-webpack';
/**
* creates a uri object
* @param {string} urlString a url string (relative or absolute)
* @param {[string]} baseString optional base for the url,
* default to window.location.href
* @return {object} url
*/
class Url {
constructor(urlString, baseString) {
var absolute = (urlString.indexOf('://') > -1);
var pathname = urlString;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.fragment = "";
this.search = "";
this.base = baseString;
if (!absolute && (typeof(baseString) !== "string")) {
this.base = window && 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.fragment = this.Url.fragment;
this.search = this.Url.search;
pathname = this.Url.pathname;
} catch (e) {
// Skip URL parsing
this.Url = undefined;
}
}
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
}
path () {
return this.Path;
};
resolve (what) {
var isAbsolute = (what.indexOf('://') > -1);
var fullpath;
if (isAbsolute) {
return what;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
};
relative (what) {
return path.relative(what, this.directory);
};
toString () {
return this.href;
};
}
export default Url

View file

@ -9,7 +9,7 @@ describe('Core', function() {
describe('Url', function () {
var Url = require('../src/core').Url;
var Url = require('../src/utils/url');
it("Url()", function() {
var url = new Url("http://example.com/fred/chasen/derf.html");
@ -61,7 +61,7 @@ describe('Core', function() {
describe('Path', function () {
var Path = require('../src/core').Path;
var Path = require('../src/utils/path');
it("Path()", function() {
var path = new Path("/fred/chasen/derf.html");

View file

@ -8,13 +8,13 @@ describe('EpubCFI', function() {
var EpubCFI = require('../src/epubcfi.js');
it('parse a cfi on init', function() {
var cfi = EpubCFI("epubcfi(/6/2[cover]!/6)");
var cfi = new EpubCFI("epubcfi(/6/2[cover]!/6)");
assert.equal( cfi.spinePos, 0, "spinePos is parsed as the first item" );
});
it('parse a cfi and ignore the base if present', function() {
var cfi = EpubCFI("epubcfi(/6/2[cover]!/6)", "/6/6[end]");
var cfi = new EpubCFI("epubcfi(/6/2[cover]!/6)", "/6/6[end]");
assert.equal( cfi.spinePos, 0, "base is ignored and spinePos is parsed as the first item" );
});
@ -56,9 +56,9 @@ describe('EpubCFI', function() {
describe('#toString()', function() {
it('parse a cfi and write it back', function() {
assert.equal(EpubCFI("epubcfi(/6/2[cover]!/6)").toString(), "epubcfi(/6/2[cover]!/6)", "output cfi string is same as input" );
assert.equal(EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)", "output cfi string is same as input" );
assert.equal(EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)", "output cfi string is same as input" );
assert.equal(new EpubCFI("epubcfi(/6/2[cover]!/6)").toString(), "epubcfi(/6/2[cover]!/6)", "output cfi string is same as input" );
assert.equal(new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)", "output cfi string is same as input" );
assert.equal(new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)", "output cfi string is same as input" );
});
});
@ -73,7 +73,7 @@ describe('EpubCFI', function() {
});
it('determine the type of a cfi', function() {
var ogcfi = EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)");
var ogcfi = new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)");
var cfi = new EpubCFI();
assert.equal( cfi.checkType(ogcfi), 'EpubCFI' );

View file

@ -2,12 +2,10 @@ var assert = require('assert');
describe('Locations', function() {
var Locations = require('../src/locations');
var core = require('../src/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml');
var core = require('../src/utils/core');
describe('#parse', function() {
var Locations = require('../src/locations');
var core = require('../src/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml');
it('parse locations from a document', function() {
@ -25,7 +23,6 @@ describe('Locations', function() {
var locations = new Locations();
var result = locations.parse(contents, "/6/4[chap01ref]", 100);
console.log(result);
assert.equal(result.length, 15);
});

View file

@ -1,19 +1,23 @@
var webpack = require("webpack");
var path = require('path');
var BabiliPlugin = require("babili-webpack-plugin");
var PROD = (process.env.NODE_ENV === 'production')
var LEGACY = (process.env.LEGACY)
var hostname = "localhost";
var port = "8080";
var enter = LEGACY ? {
"epub.legacy": ["babel-polyfill", "./libs/url/url.js", "./src/epub.js"]
} : {
"epub": "./src/epub.js",
};
module.exports = {
entry: {
epub: "./src/epub.js",
polyfills: ["./node_modules/es6-promise/dist/es6-promise.auto.js", "./libs/url/url.js"]
},
devtool: 'source-map',
entry: enter,
devtool: PROD ? false : 'source-map',
output: {
path: path.resolve("./dist"),
// path: "./dist",
filename: "[name].js",
filename: PROD ? "[name].min.js" : "[name].js",
sourceMapFilename: "[name].js.map",
library: "ePub",
libraryTarget: "umd",
@ -23,9 +27,9 @@ module.exports = {
"jszip": "JSZip",
"xmldom": "xmldom"
},
plugins: [
// new webpack.IgnorePlugin(/punycode|IPv6/),
],
plugins: PROD ? [
new BabiliPlugin()
] : [],
resolve: {
alias: {
path: "path-webpack"
@ -35,5 +39,24 @@ module.exports = {
host: hostname,
port: port,
inline: true
},
module: {
loaders: [
LEGACY ? {
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ['es2015'],
plugins: [
"add-module-exports",
]
}
} : {
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
}
}