1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-04 15:09:16 +02:00

Added locations generator to get reading percentages

This commit is contained in:
fchasen 2015-07-09 23:11:18 -04:00
parent 89a9ade8df
commit d0725af232
18 changed files with 1206 additions and 66 deletions

2
books

@ -1 +1 @@
Subproject commit ab9755a74714b647290c861f666515de220935d8
Subproject commit e9790315c2510315e270a7a4c4921825e9918039

View file

@ -2497,6 +2497,7 @@ EPUBJS.Book.prototype.unpack = function(packageXml){
book.ready.metadata.resolve(book.contents.metadata);
book.ready.cover.resolve(book.contents.cover);
book.locations = new EPUBJS.Locations(book.spine);
//-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
if(book.contents.navPath) {
@ -3422,7 +3423,7 @@ EPUBJS.Book.prototype.destroy = function() {
this.unload();
if(this.render) this.render.remove();
if(this.renderer) this.renderer.remove();
};
@ -5168,6 +5169,233 @@ EPUBJS.Layout.Fixed.prototype.calculatePages = function(){
};
};
EPUBJS.Locations = function(spine, store) {
this.spine = spine;
this.store = store;
this.epubcfi = new EPUBJS.EpubCFI();
this._locations = [];
this.total = 0;
this.break = 150;
this._current = 0;
};
EPUBJS.Locations.prototype.generate = function(chars) {
var deferred = new RSVP.defer();
var spinePos = -1;
var spineLength = this.spine.length;
var nextChapter = function(deferred){
var chapter;
var next = spinePos + 1;
var done = deferred || new RSVP.defer();
var loaded;
if(next >= spineLength) {
done.resolve();
} else {
spinePos = next;
chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
this.process(chapter).then(function() {
// Load up the next chapter
setTimeout(function(){
nextChapter(done);
}, 1);
});
}
return done.promise;
}.bind(this);
var finished = nextChapter().then(function(){
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
deferred.resolve(this._locations);
}.bind(this));
return deferred.promise;
};
EPUBJS.Locations.prototype.process = function(chapter) {
return chapter.load(this.request)
.then(function(_doc) {
var range;
var doc = _doc;
var contents = doc.documentElement;
var counter = 0;
var prev;
this.sprint(contents, function(node) {
var len = node.length;
var dist;
var pos = 0;
// Start range
if (counter === 0) {
range = doc.createRange();
range.setStart(node, 0);
}
dist = this.break - counter;
// Node is smaller than a break
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
counter = this.break;
pos += this.break;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - this.break);
// At End
} else {
// End the previous range
range.setEnd(node, pos);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = doc.createRange();
range.setStart(node, pos);
}
}
prev = node;
}.bind(this));
// Close remaining
if (range) {
range.setEnd(prev, prev.length);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
}
}.bind(this));
};
EPUBJS.Locations.prototype.sprint = function(root, func) {
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while ((node = treeWalker.nextNode())) {
func(node);
}
};
EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
};
EPUBJS.Locations.prototype.percentageFromCfi = function(cfi) {
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
EPUBJS.Locations.prototype.percentageFromLocation = function(loc) {
if (!loc || !this.total) {
return 0;
}
return Math.floor((loc / this.total ) * 100);
};
EPUBJS.Locations.prototype.cfiFromLocation = function(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(loc);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
EPUBJS.Locations.prototype.cfiFromPercentage = function(percent){
var loc = Math.ceil(this.total * (percent/100));
return this.cfiFromLocation(loc);
};
EPUBJS.Locations.prototype.load = function(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
EPUBJS.Locations.prototype.save = function(json){
return JSON.stringify(this._locations);
};
EPUBJS.Locations.prototype.getCurrent = function(json){
return this._current;
};
EPUBJS.Locations.prototype.setCurrent = function(curr){
var loc;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) {
return;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.trigger("changed", {
percentage: this.percentageFromLocation(loc)
});
};
Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
get: function () {
return this._current;
},
set: function (curr) {
this.setCurrent(curr);
}
});
RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);
EPUBJS.Pagination = function(pageList) {
this.pages = [];
this.locations = [];
@ -7306,9 +7534,20 @@ EPUBJS.replace.hrefs = function(callback, renderer){
}else{
// Links may need to be resolved, such as ../chp1.xhtml
directory = EPUBJS.core.uri(renderer.render.window.location.href).directory;
var uri = EPUBJS.core.uri(renderer.render.window.location.href);
directory = uri.directory;
if(directory) {
// We must ensure that the file:// protocol is preserved for
// local file links, as in certain contexts (such as under
// Titanium), file links without the file:// protocol will not
// work
if (uri.protocol === "file") {
relative = EPUBJS.core.resolveUrl(uri.base, href);
} else {
relative = EPUBJS.core.resolveUrl(directory, href);
}
} else {
relative = href;
}
@ -7435,6 +7674,7 @@ EPUBJS.replace.cssUrls = function(_store, base, text){
EPUBJS.Unarchiver = function(url){
this.loadLib();

File diff suppressed because one or more lines are too long

7
build/epub.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2496,6 +2496,7 @@ EPUBJS.Book.prototype.unpack = function(packageXml){
book.ready.metadata.resolve(book.contents.metadata);
book.ready.cover.resolve(book.contents.cover);
book.locations = new EPUBJS.Locations(book.spine);
//-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
if(book.contents.navPath) {
@ -3421,7 +3422,7 @@ EPUBJS.Book.prototype.destroy = function() {
this.unload();
if(this.render) this.render.remove();
if(this.renderer) this.renderer.remove();
};
@ -5167,6 +5168,233 @@ EPUBJS.Layout.Fixed.prototype.calculatePages = function(){
};
};
EPUBJS.Locations = function(spine, store) {
this.spine = spine;
this.store = store;
this.epubcfi = new EPUBJS.EpubCFI();
this._locations = [];
this.total = 0;
this.break = 150;
this._current = 0;
};
EPUBJS.Locations.prototype.generate = function(chars) {
var deferred = new RSVP.defer();
var spinePos = -1;
var spineLength = this.spine.length;
var nextChapter = function(deferred){
var chapter;
var next = spinePos + 1;
var done = deferred || new RSVP.defer();
var loaded;
if(next >= spineLength) {
done.resolve();
} else {
spinePos = next;
chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
this.process(chapter).then(function() {
// Load up the next chapter
setTimeout(function(){
nextChapter(done);
}, 1);
});
}
return done.promise;
}.bind(this);
var finished = nextChapter().then(function(){
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
deferred.resolve(this._locations);
}.bind(this));
return deferred.promise;
};
EPUBJS.Locations.prototype.process = function(chapter) {
return chapter.load(this.request)
.then(function(_doc) {
var range;
var doc = _doc;
var contents = doc.documentElement;
var counter = 0;
var prev;
this.sprint(contents, function(node) {
var len = node.length;
var dist;
var pos = 0;
// Start range
if (counter === 0) {
range = doc.createRange();
range.setStart(node, 0);
}
dist = this.break - counter;
// Node is smaller than a break
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
counter = this.break;
pos += this.break;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - this.break);
// At End
} else {
// End the previous range
range.setEnd(node, pos);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = doc.createRange();
range.setStart(node, pos);
}
}
prev = node;
}.bind(this));
// Close remaining
if (range) {
range.setEnd(prev, prev.length);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
}
}.bind(this));
};
EPUBJS.Locations.prototype.sprint = function(root, func) {
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while ((node = treeWalker.nextNode())) {
func(node);
}
};
EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
};
EPUBJS.Locations.prototype.percentageFromCfi = function(cfi) {
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
EPUBJS.Locations.prototype.percentageFromLocation = function(loc) {
if (!loc || !this.total) {
return 0;
}
return Math.floor((loc / this.total ) * 100);
};
EPUBJS.Locations.prototype.cfiFromLocation = function(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(loc);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
EPUBJS.Locations.prototype.cfiFromPercentage = function(percent){
var loc = Math.ceil(this.total * (percent/100));
return this.cfiFromLocation(loc);
};
EPUBJS.Locations.prototype.load = function(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
EPUBJS.Locations.prototype.save = function(json){
return JSON.stringify(this._locations);
};
EPUBJS.Locations.prototype.getCurrent = function(json){
return this._current;
};
EPUBJS.Locations.prototype.setCurrent = function(curr){
var loc;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) {
return;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.trigger("changed", {
percentage: this.percentageFromLocation(loc)
});
};
Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
get: function () {
return this._current;
},
set: function (curr) {
this.setCurrent(curr);
}
});
RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);
EPUBJS.Pagination = function(pageList) {
this.pages = [];
this.locations = [];
@ -7305,9 +7533,20 @@ EPUBJS.replace.hrefs = function(callback, renderer){
}else{
// Links may need to be resolved, such as ../chp1.xhtml
directory = EPUBJS.core.uri(renderer.render.window.location.href).directory;
var uri = EPUBJS.core.uri(renderer.render.window.location.href);
directory = uri.directory;
if(directory) {
// We must ensure that the file:// protocol is preserved for
// local file links, as in certain contexts (such as under
// Titanium), file links without the file:// protocol will not
// work
if (uri.protocol === "file") {
relative = EPUBJS.core.resolveUrl(uri.base, href);
} else {
relative = EPUBJS.core.resolveUrl(directory, href);
}
} else {
relative = href;
}
@ -7434,6 +7673,7 @@ EPUBJS.replace.cssUrls = function(_store, base, text){
EPUBJS.Unarchiver = function(url){
this.loadLib();

File diff suppressed because one or more lines are too long

View file

@ -8,5 +8,5 @@
"hooks/default/transculsions.js"
],
"names": [],
"mappings": "AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;A;AC9JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;A;ACxBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;A;ACrEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;A"
"mappings": "AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACxBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"
}

File diff suppressed because one or more lines are too long

2
build/reader.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

190
examples/locations.html Normal file
View file

@ -0,0 +1,190 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Basic ePubJS Example</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<meta name="apple-mobile-web-app-capable" content="yes">
<script src="../build/epub.min.js"></script>
<!-- Hooks -->
<!-- <script src="../hooks/default/transculsions.js"></script> -->
<!-- <script src="../hooks/default/endnotes.js"></script> -->
<script src="../hooks/default/smartimages.js"></script>
<script>
EPUBJS.filePath = "../reader/js/libs/";
EPUBJS.cssPath = "../reader/css/";
</script>
<style type="text/css">
body {
}
#main {
position: absolute;
width: 100%;
height: 100%;
/* overflow: hidden; */
}
#area {
width: 80%;
height: 80%;
margin: 5% auto;
max-width: 1250px;
}
#area iframe {
border: none;
}
#prev {
left: 40px;
}
#next {
right: 40px;
}
.arrow {
position: absolute;
top: 50%;
margin-top: -32px;
font-size: 64px;
color: #E2E2E2;
font-family: arial, sans-serif;
font-weight: bold;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.arrow:hover {
color: #777;
}
.arrow:active {
color: #000;
}
#controls {
position: absolute;
bottom: 16px;
left: 50%;
width: 400px;
margin-left: -200px;
text-align: center;
display: none;
}
#controls > input[type=range] {
width: 400px;
}
</style>
<script>
"use strict";
var book = ePub("../books/moby-dick/", { width: 1076, height: 588 });
</script>
</head>
<body>
<div id="main">
<div id="prev" onclick="book.prevPage();" class="arrow"></div>
<div id="area"></div>
<div id="next" onclick="book.nextPage();"class="arrow"></div>
<div id="controls">
<input id="current-percent" size="3" value="0" /> %
</div>
</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);
book.gotoCfi(cfi);
};
var throttledSlide = _.throttle(slide, 200);
var mouseDown = false;
var rendered = book.renderTo("area");
book.ready.all.then(function(){
// Load in stored locations from json or local storage
var key = book.generateBookKey()+'-locations';
var stored = false;//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(1600);
}
})
.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", throttledSlide, 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
rendered.then(function(){
// Get the current CFI
var currentLocation = book.getCurrentLocationCfi();
// 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);
book.gotoCfi(cfi);
}, false);
// Listen for location changed event, get percentage from CFI
book.on('renderer:locationChanged', function(location){
var percent = book.locations.percentageFromCfi(location);
if(!mouseDown) {
slider.value = percent;
}
currentPage.value = percent;
});
// Save out the generated locations to JSON
localStorage.setItem(book.generateBookKey()+'-locations', book.locations.save());
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

244
reader/js/epub.min.js vendored
View file

@ -2497,6 +2497,7 @@ EPUBJS.Book.prototype.unpack = function(packageXml){
book.ready.metadata.resolve(book.contents.metadata);
book.ready.cover.resolve(book.contents.cover);
book.locations = new EPUBJS.Locations(book.spine);
//-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
if(book.contents.navPath) {
@ -3422,7 +3423,7 @@ EPUBJS.Book.prototype.destroy = function() {
this.unload();
if(this.render) this.render.remove();
if(this.renderer) this.renderer.remove();
};
@ -5168,6 +5169,233 @@ EPUBJS.Layout.Fixed.prototype.calculatePages = function(){
};
};
EPUBJS.Locations = function(spine, store) {
this.spine = spine;
this.store = store;
this.epubcfi = new EPUBJS.EpubCFI();
this._locations = [];
this.total = 0;
this.break = 150;
this._current = 0;
};
EPUBJS.Locations.prototype.generate = function(chars) {
var deferred = new RSVP.defer();
var spinePos = -1;
var spineLength = this.spine.length;
var nextChapter = function(deferred){
var chapter;
var next = spinePos + 1;
var done = deferred || new RSVP.defer();
var loaded;
if(next >= spineLength) {
done.resolve();
} else {
spinePos = next;
chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
this.process(chapter).then(function() {
// Load up the next chapter
setTimeout(function(){
nextChapter(done);
}, 1);
});
}
return done.promise;
}.bind(this);
var finished = nextChapter().then(function(){
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
deferred.resolve(this._locations);
}.bind(this));
return deferred.promise;
};
EPUBJS.Locations.prototype.process = function(chapter) {
return chapter.load(this.request)
.then(function(_doc) {
var range;
var doc = _doc;
var contents = doc.documentElement;
var counter = 0;
var prev;
this.sprint(contents, function(node) {
var len = node.length;
var dist;
var pos = 0;
// Start range
if (counter === 0) {
range = doc.createRange();
range.setStart(node, 0);
}
dist = this.break - counter;
// Node is smaller than a break
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
counter = this.break;
pos += this.break;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - this.break);
// At End
} else {
// End the previous range
range.setEnd(node, pos);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = doc.createRange();
range.setStart(node, pos);
}
}
prev = node;
}.bind(this));
// Close remaining
if (range) {
range.setEnd(prev, prev.length);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
}
}.bind(this));
};
EPUBJS.Locations.prototype.sprint = function(root, func) {
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while ((node = treeWalker.nextNode())) {
func(node);
}
};
EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
};
EPUBJS.Locations.prototype.percentageFromCfi = function(cfi) {
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
EPUBJS.Locations.prototype.percentageFromLocation = function(loc) {
if (!loc || !this.total) {
return 0;
}
return Math.floor((loc / this.total ) * 100);
};
EPUBJS.Locations.prototype.cfiFromLocation = function(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(loc);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
EPUBJS.Locations.prototype.cfiFromPercentage = function(percent){
var loc = Math.ceil(this.total * (percent/100));
return this.cfiFromLocation(loc);
};
EPUBJS.Locations.prototype.load = function(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
EPUBJS.Locations.prototype.save = function(json){
return JSON.stringify(this._locations);
};
EPUBJS.Locations.prototype.getCurrent = function(json){
return this._current;
};
EPUBJS.Locations.prototype.setCurrent = function(curr){
var loc;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) {
return;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.trigger("changed", {
percentage: this.percentageFromLocation(loc)
});
};
Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
get: function () {
return this._current;
},
set: function (curr) {
this.setCurrent(curr);
}
});
RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);
EPUBJS.Pagination = function(pageList) {
this.pages = [];
this.locations = [];
@ -7306,9 +7534,20 @@ EPUBJS.replace.hrefs = function(callback, renderer){
}else{
// Links may need to be resolved, such as ../chp1.xhtml
directory = EPUBJS.core.uri(renderer.render.window.location.href).directory;
var uri = EPUBJS.core.uri(renderer.render.window.location.href);
directory = uri.directory;
if(directory) {
// We must ensure that the file:// protocol is preserved for
// local file links, as in certain contexts (such as under
// Titanium), file links without the file:// protocol will not
// work
if (uri.protocol === "file") {
relative = EPUBJS.core.resolveUrl(uri.base, href);
} else {
relative = EPUBJS.core.resolveUrl(directory, href);
}
} else {
relative = href;
}
@ -7435,6 +7674,7 @@ EPUBJS.replace.cssUrls = function(_store, base, text){
EPUBJS.Unarchiver = function(url){
this.loadLib();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -246,6 +246,7 @@ EPUBJS.Book.prototype.unpack = function(packageXml){
book.ready.metadata.resolve(book.contents.metadata);
book.ready.cover.resolve(book.contents.cover);
book.locations = new EPUBJS.Locations(book.spine);
//-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
if(book.contents.navPath) {

226
src/locations.js Normal file
View file

@ -0,0 +1,226 @@
EPUBJS.Locations = function(spine, store) {
this.spine = spine;
this.store = store;
this.epubcfi = new EPUBJS.EpubCFI();
this._locations = [];
this.total = 0;
this.break = 150;
this._current = 0;
};
EPUBJS.Locations.prototype.generate = function(chars) {
var deferred = new RSVP.defer();
var spinePos = -1;
var spineLength = this.spine.length;
var nextChapter = function(deferred){
var chapter;
var next = spinePos + 1;
var done = deferred || new RSVP.defer();
var loaded;
if(next >= spineLength) {
done.resolve();
} else {
spinePos = next;
chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
this.process(chapter).then(function() {
// Load up the next chapter
setTimeout(function(){
nextChapter(done);
}, 1);
});
}
return done.promise;
}.bind(this);
var finished = nextChapter().then(function(){
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
deferred.resolve(this._locations);
}.bind(this));
return deferred.promise;
};
EPUBJS.Locations.prototype.process = function(chapter) {
return chapter.load(this.request)
.then(function(_doc) {
var range;
var doc = _doc;
var contents = doc.documentElement;
var counter = 0;
var prev;
this.sprint(contents, function(node) {
var len = node.length;
var dist;
var pos = 0;
// Start range
if (counter === 0) {
range = doc.createRange();
range.setStart(node, 0);
}
dist = this.break - counter;
// Node is smaller than a break
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
counter = this.break;
pos += this.break;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - this.break);
// At End
} else {
// End the previous range
range.setEnd(node, pos);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = doc.createRange();
range.setStart(node, pos);
}
}
prev = node;
}.bind(this));
// Close remaining
if (range) {
range.setEnd(prev, prev.length);
cfi = chapter.cfiFromRange(range);
this._locations.push(cfi);
counter = 0;
}
}.bind(this));
};
EPUBJS.Locations.prototype.sprint = function(root, func) {
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while ((node = treeWalker.nextNode())) {
func(node);
}
};
EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
};
EPUBJS.Locations.prototype.percentageFromCfi = function(cfi) {
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
EPUBJS.Locations.prototype.percentageFromLocation = function(loc) {
if (!loc || !this.total) {
return 0;
}
return Math.floor((loc / this.total ) * 100);
};
EPUBJS.Locations.prototype.cfiFromLocation = function(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(loc);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
EPUBJS.Locations.prototype.cfiFromPercentage = function(percent){
var loc = Math.ceil(this.total * (percent/100));
return this.cfiFromLocation(loc);
};
EPUBJS.Locations.prototype.load = function(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
EPUBJS.Locations.prototype.save = function(json){
return JSON.stringify(this._locations);
};
EPUBJS.Locations.prototype.getCurrent = function(json){
return this._current;
};
EPUBJS.Locations.prototype.setCurrent = function(curr){
var loc;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) {
return;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.trigger("changed", {
percentage: this.percentageFromLocation(loc)
});
};
Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
get: function () {
return this._current;
},
set: function (curr) {
this.setCurrent(curr);
}
});
RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);