mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-05 15:32:55 +02:00
Added locations, updated cfi to handle ranges
This commit is contained in:
parent
dd14b692dc
commit
165d8a4875
8 changed files with 278 additions and 34 deletions
|
@ -139,3 +139,17 @@ body {
|
|||
#book-viewer iframe {
|
||||
padding: 40px 40px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-left: -200px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#controls > input[type=range] {
|
||||
width: 400px;
|
||||
}
|
||||
|
|
143
examples/locations.html
Normal file
143
examples/locations.html
Normal file
|
@ -0,0 +1,143 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>EPUB.js Spreads Example</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.1/jszip.min.js"></script>
|
||||
<script src="../dist/epub.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="examples.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="title"></div>
|
||||
<div id="viewer" class="spreads"></div>
|
||||
<a id="prev" href="#prev" class="arrow">‹</a>
|
||||
<a id="next" href="#next" class="arrow">›</a>
|
||||
<div id="controls">
|
||||
<input id="current-percent" size="3" value="0" /> %
|
||||
</div>
|
||||
<script>
|
||||
|
||||
var controls = document.getElementById("controls");
|
||||
var currentPage = document.getElementById("current-percent");
|
||||
var slider = document.createElement("input");
|
||||
var slide = function(){
|
||||
var cfi = book.locations.cfiFromPercentage(slider.value);
|
||||
rendition.display(cfi);
|
||||
};
|
||||
var mouseDown = false;
|
||||
|
||||
// Load the opf
|
||||
var book = ePub("../books/moby-dick/OPS/package.opf");
|
||||
var rendition = book.renderTo("viewer", {
|
||||
width: "100%",
|
||||
height: 500
|
||||
});
|
||||
|
||||
var displayed = rendition.display();
|
||||
|
||||
var title = document.getElementById("title");
|
||||
|
||||
var next = document.getElementById("next");
|
||||
next.addEventListener("click", function(e){
|
||||
rendition.next();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
var prev = document.getElementById("prev");
|
||||
prev.addEventListener("click", function(e){
|
||||
rendition.prev();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
var keyListener = function(e){
|
||||
|
||||
// Left Key
|
||||
if ((e.keyCode || e.which) == 37) {
|
||||
rendition.prev();
|
||||
}
|
||||
|
||||
// Right Key
|
||||
if ((e.keyCode || e.which) == 39) {
|
||||
rendition.next();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
rendition.on("keyup", keyListener);
|
||||
document.addEventListener("keyup", keyListener, false);
|
||||
|
||||
rendition.on("locationChanged", function(location){
|
||||
console.log(location);
|
||||
});
|
||||
|
||||
book.ready.then(function(){
|
||||
// Load in stored locations from json or local storage
|
||||
var key = book.key()+'-locations';
|
||||
var stored = localStorage.getItem(key);
|
||||
if (stored) {
|
||||
return book.locations.load(stored);
|
||||
} else {
|
||||
// Or generate the locations on the fly
|
||||
// Can pass an option number of chars to break sections by
|
||||
// default is 150 chars
|
||||
return book.locations.generate(600);
|
||||
}
|
||||
})
|
||||
.then(function(locations){
|
||||
|
||||
controls.style.display = "block";
|
||||
slider.setAttribute("type", "range");
|
||||
slider.setAttribute("min", 0);
|
||||
slider.setAttribute("max", 100);
|
||||
// slider.setAttribute("max", book.locations.total+1);
|
||||
slider.setAttribute("step", 1);
|
||||
slider.setAttribute("value", 0);
|
||||
|
||||
slider.addEventListener("change", slide, false);
|
||||
slider.addEventListener("mousedown", function(){
|
||||
mouseDown = true;
|
||||
}, false);
|
||||
slider.addEventListener("mouseup", function(){
|
||||
mouseDown = false;
|
||||
}, false);
|
||||
|
||||
// Wait for book to be rendered to get current page
|
||||
displayed.then(function(){
|
||||
// Get the current CFI
|
||||
var currentLocation = rendition.currentLocation();
|
||||
// Get the Percentage (or location) from that CFI
|
||||
var currentPage = book.locations.percentageFromCfi(currentLocation);
|
||||
slider.value = currentPage;
|
||||
currentPage.value = currentPage;
|
||||
});
|
||||
|
||||
controls.appendChild(slider);
|
||||
|
||||
currentPage.addEventListener("change", function(){
|
||||
var cfi = book.locations.cfiFromPercentage(currentPage.value/100);
|
||||
rendition.display(cfi);
|
||||
}, false);
|
||||
|
||||
// Listen for location changed event, get percentage from CFI
|
||||
rendition.on('locationChanged', function(location){
|
||||
var percent = book.locations.percentageFromCfi(location);
|
||||
var percentage = Math.floor(percent * 100);
|
||||
if(!mouseDown) {
|
||||
slider.value = percentage;
|
||||
}
|
||||
currentPage.value = percentage;
|
||||
});
|
||||
|
||||
// Save out the generated locations to JSON
|
||||
localStorage.setItem(book.key()+'-locations', book.locations.save());
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
|
||||
"start": "webpack-dev-server --inline --hot",
|
||||
"start": "webpack-dev-server --inline",
|
||||
"build": "./node_modules/.bin/gulp minify"
|
||||
},
|
||||
"author": "fchasen@gmail.com",
|
||||
|
|
15
src/book.js
15
src/book.js
|
@ -111,7 +111,7 @@ function Book(url, options){
|
|||
/**
|
||||
* @property {Locations} locations
|
||||
*/
|
||||
this.locations = new Locations(this.spine, this.load);
|
||||
this.locations = new Locations(this.spine, this.load.bind(this));
|
||||
|
||||
/**
|
||||
* @property {Navigation} navigation
|
||||
|
@ -173,7 +173,7 @@ Book.prototype.open = function(input, what){
|
|||
.then(this.openEpub.bind(this));
|
||||
} else if(type == "opf") {
|
||||
this.url = new Url(input);
|
||||
opening = this.openPackaging(input);
|
||||
opening = this.openPackaging(this.url.Path.toString());
|
||||
} else {
|
||||
this.url = new Url(input);
|
||||
opening = this.openContainer(CONTAINER_PATH)
|
||||
|
@ -223,7 +223,6 @@ Book.prototype.openContainer = function(url){
|
|||
Book.prototype.openPackaging = function(url){
|
||||
var packageUrl;
|
||||
this.path = new Path(url);
|
||||
|
||||
return this.load(url)
|
||||
.then(function(xml) {
|
||||
this.packaging = new Packaging(xml);
|
||||
|
@ -474,6 +473,16 @@ Book.prototype.range = function(cfiRange) {
|
|||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* 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:" + ePub.VERSION + ":" + ident;
|
||||
};
|
||||
|
||||
//-- Enable binding events to book
|
||||
EventEmitter(Book.prototype);
|
||||
|
||||
|
|
|
@ -304,6 +304,9 @@ EpubCFI.prototype.toString = function() {
|
|||
};
|
||||
|
||||
EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
|
||||
var stepsA, stepsB;
|
||||
var terminalA, terminalB;
|
||||
|
||||
if(typeof cfiOne === 'string') {
|
||||
cfiOne = new EpubCFI(cfiOne);
|
||||
}
|
||||
|
@ -318,36 +321,52 @@ EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (cfiOne.range) {
|
||||
stepsA = cfiOne.path.steps.concat(cfiOne.start.steps);
|
||||
terminalA = cfiOne.start.terminal;
|
||||
} else {
|
||||
stepsA = cfiOne.path.steps;
|
||||
terminalA = cfiOne.path.terminal;
|
||||
}
|
||||
|
||||
if (cfiTwo.range) {
|
||||
stepsB = cfiTwo.path.steps.concat(cfiTwo.start.steps);
|
||||
terminalB = cfiTwo.start.terminal;
|
||||
} else {
|
||||
stepsB = cfiTwo.path.steps;
|
||||
terminalB = cfiTwo.path.terminal;
|
||||
}
|
||||
|
||||
// Compare Each Step in the First item
|
||||
for (var i = 0; i < cfiOne.path.steps.length; i++) {
|
||||
if(!cfiTwo.path.steps[i]) {
|
||||
for (var i = 0; i < stepsA.length; i++) {
|
||||
if(!stepsA[i]) {
|
||||
return -1;
|
||||
}
|
||||
if(!stepsB[i]) {
|
||||
return 1;
|
||||
}
|
||||
if(cfiOne.path.steps[i].index > cfiTwo.path.steps[i].index) {
|
||||
if(stepsA[i].index > stepsB[i].index) {
|
||||
return 1;
|
||||
}
|
||||
if(cfiOne.path.steps[i].index < cfiTwo.path.steps[i].index) {
|
||||
if(stepsA[i].index < stepsB[i].index) {
|
||||
return -1;
|
||||
}
|
||||
// Otherwise continue checking
|
||||
}
|
||||
|
||||
// All steps in First equal to Second and First is Less Specific
|
||||
if(cfiOne.path.steps.length < cfiTwo.path.steps.length) {
|
||||
if(stepsA.length < stepsB.length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Compare the charecter offset of the text node
|
||||
if(cfiOne.path.terminal.offset > cfiTwo.path.terminal.offset) {
|
||||
if(terminalA.offset > terminalB.offset) {
|
||||
return 1;
|
||||
}
|
||||
if(cfiOne.path.terminal.offset < cfiTwo.path.terminal.offset) {
|
||||
if(terminalA.offset < terminalB.offset) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: compare ranges
|
||||
|
||||
// CFI's are equal
|
||||
return 0;
|
||||
};
|
||||
|
@ -485,7 +504,6 @@ EpubCFI.prototype.fromRange = function(range, base, ignoreClass) {
|
|||
}
|
||||
|
||||
cfi.start = this.pathTo(start, startOffset, ignoreClass);
|
||||
|
||||
if (needsIgnoring) {
|
||||
endOffset = this.patchOffset(end, endOffset, ignoreClass);
|
||||
}
|
||||
|
@ -504,7 +522,7 @@ EpubCFI.prototype.fromRange = function(range, base, ignoreClass) {
|
|||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (this.equalStep(cfi.start.steps[i], cfi.end.steps[i])) {
|
||||
if(i == len-1) {
|
||||
if(i === len-1) {
|
||||
// Last step is equal, check terminals
|
||||
if(cfi.start.terminal === cfi.end.terminal) {
|
||||
// CFI's are equal
|
||||
|
|
|
@ -51,7 +51,7 @@ Locations.prototype.generate = function(chars) {
|
|||
}
|
||||
|
||||
return this._locations;
|
||||
// console.log(this.precentage(this.book.rendition.location.start), this.precentage(this.book.rendition.location.end));
|
||||
// console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
|
||||
}.bind(this));
|
||||
|
||||
};
|
||||
|
@ -63,9 +63,10 @@ Locations.prototype.process = function(section) {
|
|||
|
||||
var range;
|
||||
var doc = contents.ownerDocument;
|
||||
var body = core.qs(doc, 'body');
|
||||
var counter = 0;
|
||||
|
||||
this.sprint(contents, function(node) {
|
||||
this.sprint(body, function(node) {
|
||||
var len = node.length;
|
||||
var dist;
|
||||
var pos = 0;
|
||||
|
@ -138,15 +139,17 @@ Locations.prototype.locationFromCfi = function(cfi){
|
|||
if(this._locations.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return core.locationOf(cfi, this._locations, this.epubcfi.compare);
|
||||
return core.locationOf(cfi.start, this._locations, this.epubcfi.compare);
|
||||
};
|
||||
|
||||
Locations.prototype.precentageFromCfi = function(cfi) {
|
||||
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.precentageFromLocation(loc);
|
||||
return this.percentageFromLocation(loc);
|
||||
};
|
||||
|
||||
Locations.prototype.percentageFromLocation = function(loc) {
|
||||
|
@ -214,7 +217,7 @@ Locations.prototype.setCurrent = function(curr){
|
|||
}
|
||||
|
||||
this.emit("changed", {
|
||||
percentage: this.precentageFromLocation(loc)
|
||||
percentage: this.percentageFromLocation(loc)
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -159,6 +159,15 @@ DefaultViewManager.prototype.display = function(section, target){
|
|||
this.views.clear();
|
||||
|
||||
this.add(section)
|
||||
.then(function(view){
|
||||
|
||||
// Move to correct place within the section, if needed
|
||||
if(target) {
|
||||
offset = view.locationOf(target);
|
||||
this.moveTo(offset);
|
||||
}
|
||||
|
||||
}.bind(this))
|
||||
.then(function(){
|
||||
var next;
|
||||
if (this.layout.name === "pre-paginated" &&
|
||||
|
@ -169,13 +178,7 @@ DefaultViewManager.prototype.display = function(section, target){
|
|||
}
|
||||
}
|
||||
}.bind(this))
|
||||
.then(function(view){
|
||||
|
||||
// Move to correct place within the section, if needed
|
||||
if(target) {
|
||||
offset = view.locationOf(target);
|
||||
this.moveTo(offset);
|
||||
}
|
||||
.then(function(){
|
||||
|
||||
this.views.show();
|
||||
|
||||
|
@ -362,15 +365,34 @@ DefaultViewManager.prototype.current = function(){
|
|||
};
|
||||
|
||||
DefaultViewManager.prototype.currentLocation = function(){
|
||||
|
||||
if (this.settings.axis === "vertical") {
|
||||
this.location = this.scrolledLocation();
|
||||
} else {
|
||||
this.location = this.paginatedLocation();
|
||||
}
|
||||
return this.location;
|
||||
};
|
||||
|
||||
DefaultViewManager.prototype.scrolledLocation = function(){
|
||||
var view;
|
||||
|
||||
if(this.views.length) {
|
||||
view = this.views.first();
|
||||
return this.mapping.page(view, view.section.cfiBase);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
DefaultViewManager.prototype.paginatedLocation = function(){
|
||||
var view;
|
||||
var start, end;
|
||||
|
||||
if(this.views.length) {
|
||||
view = this.views.first();
|
||||
start = container.left - view.position().left;
|
||||
end = start + this.layout.spread;
|
||||
|
||||
return this.mapping.page(view, view.section.cfiBase);
|
||||
start = this._bounds.left - view.position().left;
|
||||
end = start + this.layout.spreadWidth;
|
||||
return this.mapping.page(view, view.section.cfiBase, start, end);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -315,7 +315,7 @@ Rendition.prototype.afterDisplayed = function(view){
|
|||
* Report resize events and display the last seen location
|
||||
* @private
|
||||
*/
|
||||
Rendition.prototype.onResized = function(){
|
||||
Rendition.prototype.onResized = function(size){
|
||||
|
||||
if(this.location) {
|
||||
this.display(this.location.start);
|
||||
|
@ -454,6 +454,7 @@ Rendition.prototype.spread = function(spread, min){
|
|||
|
||||
/**
|
||||
* Report the current location
|
||||
* @private
|
||||
*/
|
||||
Rendition.prototype.reportLocation = function(){
|
||||
return this.q.enqueue(function(){
|
||||
|
@ -461,16 +462,50 @@ Rendition.prototype.reportLocation = function(){
|
|||
if (location && location.then && typeof location.then === 'function') {
|
||||
location.then(function(result) {
|
||||
this.location = result;
|
||||
|
||||
this.percentage = this.book.locations.percentageFromCfi(result);
|
||||
if (this.percentage != null) {
|
||||
this.location.percentage = this.percentage;
|
||||
}
|
||||
|
||||
this.emit("locationChanged", this.location);
|
||||
}.bind(this));
|
||||
} else if (location) {
|
||||
this.location = location;
|
||||
this.percentage = this.book.locations.percentageFromCfi(location);
|
||||
if (this.percentage != null) {
|
||||
this.location.percentage = this.percentage;
|
||||
}
|
||||
|
||||
this.emit("locationChanged", this.location);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the Current Location CFI
|
||||
* @return {EpubCFI} location (may be a promise)
|
||||
*/
|
||||
Rendition.prototype.currentLocation = function(){
|
||||
var location = this.manager.currentLocation();
|
||||
if (location && location.then && typeof location.then === 'function') {
|
||||
location.then(function(result) {
|
||||
var percentage = this.book.locations.percentageFromCfi(result);
|
||||
if (percentage != null) {
|
||||
result.percentage = percentage;
|
||||
}
|
||||
return result;
|
||||
}.bind(this));
|
||||
} else if (location) {
|
||||
var percentage = this.book.locations.percentageFromCfi(location);
|
||||
if (percentage != null) {
|
||||
location.percentage = percentage;
|
||||
}
|
||||
return location;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove and Clean Up the Rendition
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue