mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-04 15:09:16 +02:00
1066 lines
No EOL
26 KiB
JavaScript
1066 lines
No EOL
26 KiB
JavaScript
EPUBJS.Renderer = function(renderMethod, hidden) {
|
|
// Dom events to listen for
|
|
this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click"];
|
|
this.upEvent = "mouseup";
|
|
this.downEvent = "mousedown";
|
|
if('ontouchstart' in document.documentElement) {
|
|
this.listenedEvents.push("touchstart", "touchend");
|
|
this.upEvent = "touchend";
|
|
this.downEvent = "touchstart";
|
|
}
|
|
/**
|
|
* Setup a render method.
|
|
* Options are: Iframe
|
|
*/
|
|
if(renderMethod && typeof(EPUBJS.Render[renderMethod]) != "undefined"){
|
|
this.render = new EPUBJS.Render[renderMethod]();
|
|
} else {
|
|
console.error("Not a Valid Rendering Method");
|
|
}
|
|
|
|
// Listen for load events
|
|
this.render.on("render:loaded", this.loaded.bind(this));
|
|
|
|
// Cached for replacement urls from storage
|
|
this.caches = {};
|
|
|
|
// Blank Cfi for Parsing
|
|
this.epubcfi = new EPUBJS.EpubCFI();
|
|
|
|
this.spreads = true;
|
|
this.isForcedSingle = false;
|
|
this.resized = _.throttle(this.onResized.bind(this), 10);
|
|
|
|
this.layoutSettings = {};
|
|
|
|
this.hidden = hidden || false;
|
|
//-- Adds Hook methods to the Book prototype
|
|
// Hooks will all return before triggering the callback.
|
|
EPUBJS.Hooks.mixin(this);
|
|
//-- Get pre-registered hooks for events
|
|
this.getHooks("beforeChapterDisplay");
|
|
};
|
|
|
|
//-- Renderer events for listening
|
|
EPUBJS.Renderer.prototype.Events = [
|
|
"renderer:keydown",
|
|
"renderer:keyup",
|
|
"renderer:keypressed",
|
|
"renderer:mouseup",
|
|
"renderer:mousedown",
|
|
"renderer:click",
|
|
"renderer:touchstart",
|
|
"renderer:touchend",
|
|
"renderer:selected",
|
|
"renderer:chapterUnloaded",
|
|
"renderer:chapterDisplayed",
|
|
"renderer:locationChanged",
|
|
"renderer:visibleLocationChanged",
|
|
"renderer:resized",
|
|
"renderer:spreads"
|
|
];
|
|
|
|
/**
|
|
* Creates an element to render to.
|
|
* Resizes to passed width and height or to the elements size
|
|
*/
|
|
EPUBJS.Renderer.prototype.initialize = function(element, width, height){
|
|
this.container = element;
|
|
this.element = this.render.create();
|
|
|
|
this.initWidth = width;
|
|
this.initHeight = height;
|
|
|
|
this.width = width || this.container.clientWidth;
|
|
this.height = height || this.container.clientHeight;
|
|
|
|
this.container.appendChild(this.element);
|
|
|
|
if(width && height){
|
|
this.render.resize(this.width, this.height);
|
|
} else {
|
|
this.render.resize('100%', '100%');
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Display a chapter
|
|
* Takes: chapter object, global layout settings
|
|
* Returns: Promise with passed Renderer after pages has loaded
|
|
*/
|
|
EPUBJS.Renderer.prototype.displayChapter = function(chapter, globalLayout){
|
|
var store = false;
|
|
// Get the url string from the chapter (may be from storage)
|
|
return chapter.url().
|
|
then(function(url) {
|
|
|
|
// Unload the previous chapter listener
|
|
if(this.currentChapter) {
|
|
this.currentChapter.unload(); // Remove stored blobs
|
|
this.render.window.removeEventListener("resize", this.resized);
|
|
this.removeEventListeners();
|
|
this.removeSelectionListeners();
|
|
this.trigger("renderer:chapterUnloaded");
|
|
this.contents = null;
|
|
this.doc = null;
|
|
this.pageMap = null;
|
|
}
|
|
|
|
this.currentChapter = chapter;
|
|
this.chapterPos = 1;
|
|
this.pageMap = null;
|
|
this.currentChapterCfiBase = chapter.cfiBase;
|
|
|
|
this.layoutSettings = this.reconcileLayoutSettings(globalLayout, chapter.properties);
|
|
return this.load(url);
|
|
|
|
}.bind(this));
|
|
|
|
};
|
|
|
|
/**
|
|
* Loads a url (string) and renders it,
|
|
* attaching event listeners and triggering hooks.
|
|
* Returns: Promise with the rendered contents.
|
|
*/
|
|
|
|
EPUBJS.Renderer.prototype.load = function(url){
|
|
var deferred = new RSVP.defer();
|
|
var loaded;
|
|
|
|
// Switch to the required layout method for the settings
|
|
this.layoutMethod = this.determineLayout(this.layoutSettings);
|
|
this.layout = new EPUBJS.Layout[this.layoutMethod]();
|
|
|
|
this.visible(false);
|
|
|
|
render = this.render.load(url);
|
|
|
|
render.then(function(contents) {
|
|
var formated;
|
|
this.currentChapter.contents = this.render.document;
|
|
this.contents = contents;
|
|
this.doc = this.render.document;
|
|
|
|
// Format the contents using the current layout method
|
|
this.formated = this.layout.format(contents, this.render.width, this.render.height, this.gap);
|
|
this.render.setPageDimensions(this.formated.pageWidth, this.formated.pageHeight);
|
|
|
|
if(!this.initWidth && !this.initHeight){
|
|
this.render.window.addEventListener("resize", this.resized, false);
|
|
}
|
|
|
|
|
|
this.addEventListeners();
|
|
this.addSelectionListeners();
|
|
|
|
//-- Trigger registered hooks before displaying
|
|
this.beforeDisplay(function(){
|
|
var pages = this.layout.calculatePages();
|
|
var msg = this.currentChapter;
|
|
|
|
this.updatePages(pages);
|
|
|
|
this.visibleRangeCfi = this.getVisibleRangeCfi();
|
|
this.currentLocationCfi = this.visibleRangeCfi.start;
|
|
this.trigger("renderer:locationChanged", this.currentLocationCfi);
|
|
this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
|
|
|
|
msg.cfi = this.currentLocationCfi;
|
|
this.trigger("renderer:chapterDisplayed", msg);
|
|
|
|
this.visible(true);
|
|
|
|
deferred.resolve(this); //-- why does this return the renderer?
|
|
}.bind(this));
|
|
|
|
}.bind(this));
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.loaded = function(url){
|
|
this.trigger("render:loaded", url);
|
|
// var uri = EPUBJS.core.uri(url);
|
|
// var relative = uri.path.replace(book.bookUrl, '');
|
|
// console.log(url, uri, relative);
|
|
};
|
|
|
|
/**
|
|
* Reconciles the current chapters layout properies with
|
|
* the global layout properities.
|
|
* Takes: global layout settings object, chapter properties string
|
|
* Returns: Object with layout properties
|
|
*/
|
|
EPUBJS.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){
|
|
var settings = {};
|
|
|
|
//-- Get the global defaults
|
|
for (var attr in global) {
|
|
if (global.hasOwnProperty(attr)){
|
|
settings[attr] = global[attr];
|
|
}
|
|
}
|
|
//-- Get the chapter's display type
|
|
chapter.forEach(function(prop){
|
|
var rendition = prop.replace("rendition:", '');
|
|
var split = rendition.indexOf("-");
|
|
var property, value;
|
|
|
|
if(split != -1){
|
|
property = rendition.slice(0, split);
|
|
value = rendition.slice(split+1);
|
|
|
|
settings[property] = value;
|
|
}
|
|
});
|
|
return settings;
|
|
};
|
|
|
|
/**
|
|
* Uses the settings to determine which Layout Method is needed
|
|
* Triggers events based on the method choosen
|
|
* Takes: Layout settings object
|
|
* Returns: String of appropriate for EPUBJS.Layout function
|
|
*/
|
|
EPUBJS.Renderer.prototype.determineLayout = function(settings){
|
|
// Default is layout: reflowable & spread: auto
|
|
var spreads = this.determineSpreads(this.minSpreadWidth);
|
|
var layoutMethod = spreads ? "ReflowableSpreads" : "Reflowable";
|
|
var scroll = false;
|
|
|
|
if(settings.layout === "pre-paginated") {
|
|
layoutMethod = "Fixed";
|
|
scroll = true;
|
|
spreads = false;
|
|
}
|
|
|
|
if(settings.layout === "reflowable" && settings.spread === "none") {
|
|
layoutMethod = "Reflowable";
|
|
scroll = false;
|
|
spreads = false;
|
|
}
|
|
|
|
if(settings.layout === "reflowable" && settings.spread === "both") {
|
|
layoutMethod = "ReflowableSpreads";
|
|
scroll = false;
|
|
spreads = true;
|
|
}
|
|
|
|
this.spreads = spreads;
|
|
this.render.scroll(scroll);
|
|
this.trigger("renderer:spreads", spreads);
|
|
return layoutMethod;
|
|
};
|
|
|
|
// Shortcut to trigger the hook before displaying the chapter
|
|
EPUBJS.Renderer.prototype.beforeDisplay = function(callback, renderer){
|
|
this.triggerHooks("beforeChapterDisplay", callback, this);
|
|
};
|
|
|
|
// Update the renderer with the information passed by the layout
|
|
EPUBJS.Renderer.prototype.updatePages = function(layout){
|
|
this.pageMap = this.mapPage();
|
|
this.displayedPages = layout.displayedPages;
|
|
this.currentChapter.pages = layout.pageCount;
|
|
};
|
|
|
|
// Apply the layout again and jump back to the previous cfi position
|
|
EPUBJS.Renderer.prototype.reformat = function(){
|
|
var renderer = this;
|
|
var formated, pages;
|
|
if(!this.contents) return;
|
|
|
|
this.layoutMethod = this.determineLayout(this.layoutSettings);
|
|
this.layout = new EPUBJS.Layout[this.layoutMethod]();
|
|
|
|
this.formated = this.layout.format(this.contents, this.render.width, this.render.height, this.gap);
|
|
this.render.setPageDimensions(this.formated.pageWidth, this.formated.pageHeight);
|
|
|
|
pages = renderer.layout.calculatePages();
|
|
renderer.updatePages(pages);
|
|
|
|
// Give the css styles time to update
|
|
clearTimeout(this.timeoutTillCfi);
|
|
this.timeoutTillCfi = setTimeout(function(){
|
|
//-- Go to current page after formating
|
|
if(renderer.currentLocationCfi){
|
|
renderer.gotoCfi(renderer.currentLocationCfi);
|
|
}
|
|
this.timeoutTillCfi = null;
|
|
}, 10);
|
|
|
|
};
|
|
|
|
// Hide and show the render's container .
|
|
EPUBJS.Renderer.prototype.visible = function(bool){
|
|
if(typeof(bool) === "undefined") {
|
|
return this.element.style.visibility;
|
|
}
|
|
|
|
if(bool === true && !this.hidden){
|
|
this.element.style.visibility = "visible";
|
|
}else if(bool === false){
|
|
this.element.style.visibility = "hidden";
|
|
}
|
|
};
|
|
|
|
// Remove the render element and clean up listeners
|
|
EPUBJS.Renderer.prototype.remove = function() {
|
|
if(this.render.window) {
|
|
this.render.unload();
|
|
this.render.window.removeEventListener("resize", this.resized);
|
|
this.removeEventListeners();
|
|
this.removeSelectionListeners();
|
|
}
|
|
|
|
this.container.removeChild(this.element);
|
|
};
|
|
|
|
//-- STYLES
|
|
|
|
EPUBJS.Renderer.prototype.applyStyles = function(styles) {
|
|
for (var style in styles) {
|
|
this.render.setStyle(style, styles[style]);
|
|
}
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.setStyle = function(style, val, prefixed){
|
|
this.render.setStyle(style, val, prefixed);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.removeStyle = function(style){
|
|
this.render.removeStyle(style);
|
|
};
|
|
|
|
//-- HEAD TAGS
|
|
EPUBJS.Renderer.prototype.applyHeadTags = function(headTags) {
|
|
for ( var headTag in headTags ) {
|
|
this.render.addHeadTag(headTag, headTags[headTag]);
|
|
}
|
|
};
|
|
|
|
//-- NAVIGATION
|
|
|
|
EPUBJS.Renderer.prototype.page = function(pg){
|
|
if(pg >= 1 && pg <= this.displayedPages){
|
|
this.chapterPos = pg;
|
|
|
|
this.render.page(pg);
|
|
this.visibleRangeCfi = this.getVisibleRangeCfi();
|
|
this.currentLocationCfi = this.visibleRangeCfi.start;
|
|
this.trigger("renderer:locationChanged", this.currentLocationCfi);
|
|
this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
|
|
|
|
return true;
|
|
}
|
|
//-- Return false if page is greater than the total
|
|
return false;
|
|
};
|
|
|
|
// Short cut to find next page's cfi starting at the last visible element
|
|
/*
|
|
EPUBJS.Renderer.prototype.nextPage = function(){
|
|
var pg = this.chapterPos + 1;
|
|
if(pg <= this.displayedPages){
|
|
this.chapterPos = pg;
|
|
|
|
this.render.page(pg);
|
|
|
|
this.currentLocationCfi = this.getPageCfi(this.visibileEl);
|
|
this.trigger("renderer:locationChanged", this.currentLocationCfi);
|
|
|
|
return true;
|
|
}
|
|
//-- Return false if page is greater than the total
|
|
return false;
|
|
};
|
|
*/
|
|
EPUBJS.Renderer.prototype.nextPage = function(){
|
|
return this.page(this.chapterPos + 1);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.prevPage = function(){
|
|
return this.page(this.chapterPos - 1);
|
|
};
|
|
|
|
//-- Show the page containing an Element
|
|
EPUBJS.Renderer.prototype.pageByElement = function(el){
|
|
var pg;
|
|
if(!el) return;
|
|
|
|
pg = this.render.getPageNumberByElement(el);
|
|
this.page(pg);
|
|
};
|
|
|
|
// Jump to the last page of the chapter
|
|
EPUBJS.Renderer.prototype.lastPage = function(){
|
|
this.page(this.displayedPages);
|
|
};
|
|
|
|
//-- Find a section by fragement id
|
|
EPUBJS.Renderer.prototype.section = function(fragment){
|
|
var el = this.doc.getElementById(fragment),
|
|
left, pg;
|
|
|
|
if(el){
|
|
this.pageByElement(el);
|
|
}
|
|
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.firstElementisTextNode = function(node) {
|
|
var children = node.childNodes;
|
|
var leng = children.length;
|
|
|
|
if(leng &&
|
|
children[0] && // First Child
|
|
children[0].nodeType === 3 && // This is a textNodes
|
|
children[0].textContent.trim().length) { // With non whitespace or return charecters
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Walk the node tree from a start element to next visible element
|
|
EPUBJS.Renderer.prototype.walk = function(node, x, y) {
|
|
var r, children, leng,
|
|
startNode = node,
|
|
prevNode,
|
|
stack = [startNode];
|
|
|
|
var STOP = 10000, ITER=0;
|
|
|
|
while(!r && stack.length) {
|
|
node = stack.shift();
|
|
if( this.containsPoint(node, x, y) && this.firstElementisTextNode(node)) {
|
|
r = node;
|
|
}
|
|
|
|
if(!r && node && node.childElementCount > 0){
|
|
children = node.children;
|
|
if (children && children.length) {
|
|
leng = children.length ? children.length : 0;
|
|
} else {
|
|
return r;
|
|
}
|
|
for (var i = leng-1; i >= 0; i--) {
|
|
if(children[i] != prevNode) stack.unshift(children[i]);
|
|
}
|
|
}
|
|
|
|
if(!r && stack.length === 0 && startNode && startNode.parentNode !== null){
|
|
stack.push(startNode.parentNode);
|
|
prevNode = startNode;
|
|
startNode = startNode.parentNode;
|
|
}
|
|
|
|
|
|
ITER++;
|
|
if(ITER > STOP) {
|
|
console.error("ENDLESS LOOP");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return r;
|
|
};
|
|
|
|
// Checks if an element is on the screen
|
|
EPUBJS.Renderer.prototype.containsPoint = function(el, x, y){
|
|
var rect;
|
|
var left;
|
|
if(el && typeof el.getBoundingClientRect === 'function'){
|
|
rect = el.getBoundingClientRect();
|
|
// console.log(el, rect, x, y);
|
|
|
|
if( rect.width !== 0 &&
|
|
rect.height !== 0 && // Element not visible
|
|
rect.left >= x &&
|
|
x <= rect.left + rect.width) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.textSprint = function(root, func) {
|
|
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
acceptNode: function (node) {
|
|
if ( ! /^\s*$/.test(node.data) ) {
|
|
return NodeFilter.FILTER_ACCEPT;
|
|
} else {
|
|
return NodeFilter.FILTER_REJECT;
|
|
}
|
|
}
|
|
}, false);
|
|
var node;
|
|
while ((node = treeWalker.nextNode())) {
|
|
func(node);
|
|
}
|
|
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.sprint = function(root, func) {
|
|
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, false, false);
|
|
var node;
|
|
while ((node = treeWalker.nextNode())) {
|
|
func(node);
|
|
}
|
|
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.mapPage = function(){
|
|
var renderer = this;
|
|
var map = [{ start: null, end: null }];
|
|
var root = this.render.getBaseElement();
|
|
var page = 1;
|
|
var width = this.layout.colWidth + this.layout.gap;
|
|
var offset = this.formated.pageWidth * (this.chapterPos-1);
|
|
var limit = (width * page) - offset;// (width * page) - offset;
|
|
var elLimit = 0;
|
|
var prevRange;
|
|
var cfi;
|
|
var check = function(node) {
|
|
var elPos;
|
|
var children = Array.prototype.slice.call(node.childNodes);
|
|
if (node.nodeType == Node.ELEMENT_NODE) {
|
|
elPos = node.getBoundingClientRect();
|
|
if(elPos.left + elPos.width > elLimit) {
|
|
|
|
// console.log("checking text node of: ", node);
|
|
children.forEach(function(node){
|
|
if(node.nodeType == Node.TEXT_NODE &&
|
|
node.textContent.trim().length) {
|
|
checkText(node);
|
|
}
|
|
});
|
|
// renderer.textSprint(node, checkText);
|
|
}
|
|
}
|
|
|
|
// if (node.nodeType != Node.TEXT_NODE) {
|
|
// renderer.handleTextNode(node, map, prevRange, limit, cfi);
|
|
// }
|
|
};
|
|
var checkText = function(node){
|
|
var ranges = renderer.splitTextNodeIntoWordsRanges(node);
|
|
ranges.forEach(function(range){
|
|
var pos = range.getBoundingClientRect();
|
|
// console.log(pos.left, pos.top, node);
|
|
|
|
// if(!pos || (pos.width === 0 && pos.height === 0)) {
|
|
// return;
|
|
// }
|
|
|
|
if(pos.left + pos.width < limit) {
|
|
if(!map[page-1].start){
|
|
range.collapse(true);
|
|
cfi = renderer.currentChapter.cfiFromRange(range);
|
|
map[page-1].start = cfi;
|
|
}
|
|
} else {
|
|
if(prevRange){
|
|
prevRange.collapse(true);
|
|
cfi = renderer.currentChapter.cfiFromRange(prevRange);
|
|
map[page-1].end = cfi;
|
|
}
|
|
|
|
range.collapse(true);
|
|
cfi = renderer.currentChapter.cfiFromRange(range);
|
|
map.push({
|
|
start: cfi,
|
|
end: null
|
|
});
|
|
page += 1;
|
|
limit = (width * page) - offset;
|
|
elLimit = limit;
|
|
}
|
|
|
|
prevRange = range;
|
|
});
|
|
|
|
|
|
};
|
|
|
|
this.sprint(root, check);
|
|
// this.textSprint(root, checkText);
|
|
|
|
if(prevRange){
|
|
prevRange.collapse(true);
|
|
|
|
cfi = renderer.currentChapter.cfiFromRange(prevRange);
|
|
map[page-1].end = cfi;
|
|
}
|
|
|
|
// Handle empty map
|
|
if(map.length === 1 && !map[0].start) {
|
|
range = this.doc.createRange();
|
|
range.selectNodeContents(root);
|
|
range.collapse(true);
|
|
|
|
cfi = renderer.currentChapter.cfiFromRange(range);
|
|
map[0].start = cfi;
|
|
map[0].end = cfi;
|
|
}
|
|
|
|
// clean up
|
|
prevRange = null;
|
|
ranges = null;
|
|
range = null;
|
|
root = null;
|
|
return map;
|
|
};
|
|
|
|
|
|
EPUBJS.Renderer.prototype.splitTextNodeIntoWordsRanges = function(node){
|
|
var ranges = [];
|
|
var text = node.textContent.trim();
|
|
var range;
|
|
var rect;
|
|
var list;
|
|
pos = text.indexOf(" ");
|
|
|
|
if(pos === -1) {
|
|
range = this.doc.createRange();
|
|
range.selectNodeContents(node);
|
|
return [range];
|
|
}
|
|
|
|
range = this.doc.createRange();
|
|
range.setStart(node, 0);
|
|
range.setEnd(node, pos);
|
|
ranges.push(range);
|
|
range = false;
|
|
|
|
while ( pos != -1 ) {
|
|
|
|
pos = text.indexOf(" ", pos + 1);
|
|
if(pos > 0) {
|
|
|
|
if(range) {
|
|
range.setEnd(node, pos);
|
|
ranges.push(range);
|
|
}
|
|
|
|
range = this.doc.createRange();
|
|
range.setStart(node, pos+1);
|
|
}
|
|
}
|
|
|
|
if(range) {
|
|
range.setEnd(node, text.length);
|
|
ranges.push(range);
|
|
}
|
|
|
|
return ranges;
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.rangePosition = function(range){
|
|
var rect;
|
|
var list;
|
|
|
|
list = range.getClientRects();
|
|
|
|
if(list.length) {
|
|
rect = list[0];
|
|
return rect;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
/*
|
|
// Get the cfi of the current page
|
|
EPUBJS.Renderer.prototype.getPageCfi = function(prevEl){
|
|
var range = this.doc.createRange();
|
|
var position;
|
|
// TODO : this might need to take margin / padding into account?
|
|
var x = 1;//this.formated.pageWidth/2;
|
|
var y = 1;//;this.formated.pageHeight/2;
|
|
|
|
range = this.getRange(x, y);
|
|
|
|
// var test = this.doc.defaultView.getSelection();
|
|
// var r = this.doc.createRange();
|
|
// test.removeAllRanges();
|
|
// r.setStart(range.startContainer, range.startOffset);
|
|
// r.setEnd(range.startContainer, range.startOffset + 1);
|
|
// test.addRange(r);
|
|
|
|
return this.currentChapter.cfiFromRange(range);
|
|
};
|
|
*/
|
|
|
|
// Get the cfi of the current page
|
|
EPUBJS.Renderer.prototype.getPageCfi = function(){
|
|
var pg;
|
|
if (this.spreads) {
|
|
pg = this.chapterPos*2;
|
|
startRange = this.pageMap[pg-2];
|
|
} else {
|
|
pg = this.chapterPos;
|
|
startRange = this.pageMap[pg-1];
|
|
}
|
|
return this.pageMap[(this.chapterPos * 2) -1].start;
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.getRange = function(x, y, forceElement){
|
|
var range = this.doc.createRange();
|
|
var position;
|
|
forceElement = true; // temp override
|
|
if(typeof document.caretPositionFromPoint !== "undefined" && !forceElement){
|
|
position = this.doc.caretPositionFromPoint(x, y);
|
|
range.setStart(position.offsetNode, position.offset);
|
|
} else if(typeof document.caretRangeFromPoint !== "undefined" && !forceElement){
|
|
range = this.doc.caretRangeFromPoint(x, y);
|
|
} else {
|
|
this.visibileEl = this.findElementAfter(x, y);
|
|
range.setStart(this.visibileEl, 1);
|
|
}
|
|
|
|
// var test = this.doc.defaultView.getSelection();
|
|
// var r = this.doc.createRange();
|
|
// test.removeAllRanges();
|
|
// r.setStart(range.startContainer, range.startOffset);
|
|
// r.setEnd(range.startContainer, range.startOffset + 1);
|
|
// test.addRange(r);
|
|
return range;
|
|
};
|
|
|
|
/*
|
|
EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(prevEl){
|
|
var startX = 0;
|
|
var startY = 0;
|
|
var endX = this.width-1;
|
|
var endY = this.height-1;
|
|
var startRange = this.getRange(startX, startY);
|
|
var endRange = this.getRange(endX, endY); //fix if carret not avail
|
|
var startCfi = this.currentChapter.cfiFromRange(startRange);
|
|
var endCfi;
|
|
if(endRange) {
|
|
endCfi = this.currentChapter.cfiFromRange(endRange);
|
|
}
|
|
|
|
return {
|
|
start: startCfi,
|
|
end: endCfi || false
|
|
};
|
|
};
|
|
*/
|
|
|
|
EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(){
|
|
var pg;
|
|
var startRange, endRange;
|
|
|
|
if(!this.pageMap) {
|
|
console.warn("page map not loaded");
|
|
return false;
|
|
}
|
|
|
|
if (this.spreads) {
|
|
pg = this.chapterPos*2;
|
|
startRange = this.pageMap[pg-2];
|
|
endRange = startRange;
|
|
|
|
if(this.layout.pageCount > 1) {
|
|
endRange = this.pageMap[pg-1];
|
|
}
|
|
} else {
|
|
pg = this.chapterPos;
|
|
startRange = this.pageMap[pg-1];
|
|
endRange = startRange;
|
|
}
|
|
|
|
if(!startRange) {
|
|
console.warn("startRange miss:", this.pageMap, pg);
|
|
startRange = this.pageMap[this.pageMap.length-1];
|
|
endRange = startRange;
|
|
}
|
|
|
|
return {
|
|
start: startRange.start,
|
|
end: endRange.end
|
|
};
|
|
};
|
|
|
|
// Goto a cfi position in the current chapter
|
|
EPUBJS.Renderer.prototype.gotoCfi = function(cfi){
|
|
var pg;
|
|
var marker;
|
|
var range;
|
|
|
|
if(_.isString(cfi)){
|
|
cfi = this.epubcfi.parse(cfi);
|
|
}
|
|
|
|
if(typeof document.evaluate === 'undefined') {
|
|
marker = this.epubcfi.addMarker(cfi, this.doc);
|
|
if(marker) {
|
|
pg = this.render.getPageNumberByElement(marker);
|
|
// Must Clean up Marker before going to page
|
|
this.epubcfi.removeMarker(marker, this.doc);
|
|
this.page(pg);
|
|
}
|
|
} else {
|
|
range = this.epubcfi.generateRangeFromCfi(cfi, this.doc);
|
|
if(range) {
|
|
pg = this.render.getPageNumberByRect(range.getBoundingClientRect());
|
|
this.page(pg);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Walk nodes until a visible element is found
|
|
EPUBJS.Renderer.prototype.findFirstVisible = function(startEl){
|
|
var el = startEl || this.render.getBaseElement();
|
|
var found;
|
|
found = this.walk(el);
|
|
|
|
if(found) {
|
|
return found;
|
|
}else{
|
|
return startEl;
|
|
}
|
|
|
|
};
|
|
// TODO: remove me - unsused
|
|
EPUBJS.Renderer.prototype.findElementAfter = function(x, y, startEl){
|
|
var el = startEl || this.render.getBaseElement();
|
|
var found;
|
|
found = this.walk(el, x, y);
|
|
if(found) {
|
|
return found;
|
|
}else{
|
|
return el;
|
|
}
|
|
|
|
};
|
|
|
|
/*
|
|
EPUBJS.Renderer.prototype.route = function(hash, callback){
|
|
var location = window.location.hash.replace('#/', '');
|
|
if(this.useHash && location.length && location != this.prevLocation){
|
|
this.show(location, callback);
|
|
this.prevLocation = location;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EPUBJS.Renderer.prototype.hideHashChanges = function(){
|
|
this.useHash = false;
|
|
}
|
|
|
|
*/
|
|
|
|
EPUBJS.Renderer.prototype.resize = function(width, height, setSize){
|
|
var spreads;
|
|
|
|
this.width = width;
|
|
this.height = height;
|
|
|
|
if(setSize !== false) {
|
|
this.render.resize(this.width, this.height);
|
|
}
|
|
|
|
spreads = this.determineSpreads(this.minSpreadWidth);
|
|
// Only re-layout if the spreads have switched
|
|
if(spreads != this.spreads){
|
|
this.spreads = spreads;
|
|
this.layoutMethod = this.determineLayout(this.layoutSettings);
|
|
this.layout = new EPUBJS.Layout[this.layoutMethod]();
|
|
}
|
|
|
|
if(this.contents){
|
|
this.reformat();
|
|
}
|
|
|
|
this.trigger("renderer:resized", {
|
|
width: this.width,
|
|
height: this.height
|
|
});
|
|
};
|
|
|
|
//-- Listeners for events in the frame
|
|
|
|
EPUBJS.Renderer.prototype.onResized = function(e) {
|
|
var width = this.container.clientWidth;
|
|
var height = this.container.clientHeight;
|
|
|
|
this.resize(width, height, false);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.addEventListeners = function(){
|
|
|
|
this.listenedEvents.forEach(function(eventName){
|
|
this.render.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
|
|
}, this);
|
|
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.removeEventListeners = function(){
|
|
|
|
this.listenedEvents.forEach(function(eventName){
|
|
this.render.document.removeEventListener(eventName, this.triggerEvent, false);
|
|
}, this);
|
|
|
|
};
|
|
|
|
// Pass browser events
|
|
EPUBJS.Renderer.prototype.triggerEvent = function(e){
|
|
this.trigger("renderer:"+e.type, e);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.addSelectionListeners = function(){
|
|
this.render.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.removeSelectionListeners = function(){
|
|
this.doc.removeEventListener("selectionchange", this.onSelectionChange, false);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.onSelectionChange = function(e){
|
|
if (this.selectionEndTimeout) {
|
|
clearTimeout(this.selectionEndTimeout);
|
|
}
|
|
this.selectionEndTimeout = setTimeout(function() {
|
|
this.selectedRange = this.render.window.getSelection();
|
|
this.trigger("renderer:selected", this.selectedRange);
|
|
}.bind(this), 500);
|
|
};
|
|
|
|
|
|
//-- Spreads
|
|
|
|
EPUBJS.Renderer.prototype.setMinSpreadWidth = function(width){
|
|
this.minSpreadWidth = width;
|
|
this.spreads = this.determineSpreads(width);
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.determineSpreads = function(cutoff){
|
|
if(this.isForcedSingle || !cutoff || this.width < cutoff) {
|
|
return false; //-- Single Page
|
|
}else{
|
|
return true; //-- Double Page
|
|
}
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.forceSingle = function(bool){
|
|
if(bool) {
|
|
this.isForcedSingle = true;
|
|
this.spreads = false;
|
|
} else {
|
|
this.isForcedSingle = false;
|
|
this.spreads = this.determineSpreads(this.minSpreadWidth);
|
|
}
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.setGap = function(gap){
|
|
this.gap = gap; //-- False == auto gap
|
|
};
|
|
|
|
//-- Content Replacements
|
|
|
|
EPUBJS.Renderer.prototype.replace = function(query, func, finished, progress){
|
|
var items = this.contents.querySelectorAll(query),
|
|
resources = Array.prototype.slice.call(items),
|
|
count = resources.length,
|
|
after = function(result, full){
|
|
count--;
|
|
if(progress) progress(result, full, count);
|
|
if(count <= 0 && finished) finished(true);
|
|
};
|
|
|
|
if(count === 0) {
|
|
finished(false);
|
|
return;
|
|
}
|
|
resources.forEach(function(item){
|
|
|
|
func(item, after);
|
|
|
|
}.bind(this));
|
|
|
|
};
|
|
|
|
EPUBJS.Renderer.prototype.replaceWithStored = function(query, attr, func, callback) {
|
|
var _oldUrls,
|
|
_newUrls = {},
|
|
_store = this.currentChapter.store,
|
|
_cache = this.caches[query],
|
|
_uri = EPUBJS.core.uri(this.currentChapter.absolute),
|
|
_chapterBase = _uri.base,
|
|
_attr = attr,
|
|
_wait = 2000,
|
|
progress = function(url, full, count) {
|
|
_newUrls[full] = url;
|
|
},
|
|
finished = function(notempty) {
|
|
if(callback) callback();
|
|
|
|
_.each(_oldUrls, function(url){
|
|
_store.revokeUrl(url);
|
|
});
|
|
|
|
_cache = _newUrls;
|
|
};
|
|
|
|
if(!_store) return;
|
|
|
|
if(!_cache) _cache = {};
|
|
_oldUrls = _.clone(_cache);
|
|
|
|
this.replace(query, function(link, done){
|
|
var src = link.getAttribute(_attr),
|
|
full = EPUBJS.core.resolveUrl(_chapterBase, src);
|
|
|
|
var replaceUrl = function(url) {
|
|
var timeout;
|
|
|
|
link.onload = function(){
|
|
clearTimeout(timeout);
|
|
done(url, full);
|
|
};
|
|
|
|
link.onerror = function(e){
|
|
clearTimeout(timeout);
|
|
done(url, full);
|
|
console.error(e);
|
|
};
|
|
|
|
if(query == "image") {
|
|
//-- SVG needs this to trigger a load event
|
|
link.setAttribute("externalResourcesRequired", "true");
|
|
}
|
|
|
|
if(query == "link[href]") {
|
|
//-- Only Stylesheet links seem to have a load events, just continue others
|
|
done(url, full);
|
|
}
|
|
|
|
link.setAttribute(_attr, url);
|
|
|
|
//-- If elements never fire Load Event, should continue anyways
|
|
timeout = setTimeout(function(){
|
|
done(url, full);
|
|
}, _wait);
|
|
|
|
};
|
|
|
|
if(full in _oldUrls){
|
|
replaceUrl(_oldUrls[full]);
|
|
_newUrls[full] = _oldUrls[full];
|
|
delete _oldUrls[full];
|
|
}else{
|
|
func(_store, full, replaceUrl, link);
|
|
}
|
|
|
|
}, finished, progress);
|
|
};
|
|
|
|
//-- Enable binding events to Renderer
|
|
RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype); |