diff --git a/demo/dev.html b/demo/dev.html index 9374660..b0ebf11 100755 --- a/demo/dev.html +++ b/demo/dev.html @@ -25,7 +25,7 @@ EPUBJS.cssPath = "css/"; // fileStorage.filePath = EPUBJS.filePath; - window.Reader = ePubReader("moby-dick/", { reload: true, generatePagination: true }); + window.Reader = ePubReader("moby-dick/", { reload: true, generatePagination: false }); } }; diff --git a/examples/pagination.html b/examples/pagination.html index 049c2ca..f9af76d 100644 --- a/examples/pagination.html +++ b/examples/pagination.html @@ -57,7 +57,7 @@ position: absolute; width: 100%; height: 100%; - overflow: hidden; + /* overflow: hidden; */ } #area { @@ -108,9 +108,10 @@ width: 400px; margin-left: -200px; text-align: center; + display: none; } - #controls > input { + #controls > input[type=range] { width: 400px; } @@ -118,7 +119,7 @@ @@ -127,23 +128,27 @@
-
+
+ / 0 +
diff --git a/src/book.js b/src/book.js index 024326e..0df6027 100644 --- a/src/book.js +++ b/src/book.js @@ -75,6 +75,7 @@ EPUBJS.Book = function(options){ this.ready.toc.promise ]; + this.pageList = []; this.pagination = new EPUBJS.Pagination(); this.pageListReady = this.ready.pageList.promise; @@ -249,8 +250,8 @@ EPUBJS.Book.prototype.unpack = function(packageXml){ then(function(navHtml){ return parse.pageList(navHtml, book.spineIndexByURL, book.spine); }).then(function(pageList){ - book.pageList = book.contents.pageList = pageList; if(pageList.length) { + book.pageList = book.contents.pageList = pageList; book.pagination.process(book.pageList); book.ready.pageList.resolve(book.pageList); } @@ -287,6 +288,9 @@ EPUBJS.Book.prototype.createHiddenRender = function(renderer, _width, _height) { hiddenEl = document.createElement("div"); hiddenEl.style.visibility = "hidden"; + hiddenEl.style.overflow = "hidden"; + hiddenEl.style.width = "0"; + hiddenEl.style.height = "0"; this.element.appendChild(hiddenEl); renderer.initialize(hiddenEl, width, height); @@ -314,8 +318,8 @@ EPUBJS.Book.prototype.generatePageList = function(width, height){ spinePos = next; chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store); - pager.displayChapter(chapter, this.globalLayoutProperties).then(function(){ - var nextPage = pager.nextPage(); + pager.displayChapter(chapter, this.globalLayoutProperties).then(function(chap){ + var nextPage = true;//pager.nextPage(); // Page though the entire chapter while (nextPage) { nextPage = pager.nextPage(); @@ -368,10 +372,7 @@ EPUBJS.Book.prototype.loadPagination = function(pagelistJSON) { if(pageList && pageList.length) { this.pageList = pageList; this.pagination.process(this.pageList); - // Wait for book contents to load before resolving - this.ready.all.then(function(){ - this.ready.pageList.resolve(this.pageList); - }.bind(this)); + this.ready.pageList.resolve(this.pageList); } return this.pageList; }; @@ -425,8 +426,22 @@ EPUBJS.Book.prototype.listenToRenderer = function(renderer){ "page": page, "percentage": percent }); + + // TODO: Add event for first and last page. + // (though last is going to be hard, since it could be several reflowed pages long) } }.bind(this)); + + renderer.on("render:loaded", this.loadChange.bind(this)); +}; + +// Listens for load events from the Renderer and checks against the current chapter +// Prevents the Render from loading a different chapter when back button is pressed +EPUBJS.Book.prototype.loadChange = function(url){ + var uri = EPUBJS.core.uri(url); + if(this.currentChapter && uri.path != this.currentChapter.absolute){ + this.goto(uri.filename); + } }; EPUBJS.Book.prototype.unlistenToRenderer = function(renderer){ @@ -975,7 +990,7 @@ EPUBJS.Book.prototype.applyHeadTags = function(callback){ EPUBJS.Book.prototype._registerReplacements = function(renderer){ renderer.registerHook("beforeChapterDisplay", this.applyStyles.bind(this), true); renderer.registerHook("beforeChapterDisplay", this.applyHeadTags.bind(this), true); - renderer.registerHook("beforeChapterDisplay", EPUBJS.replace.hrefs, true); + renderer.registerHook("beforeChapterDisplay", EPUBJS.replace.hrefs.bind(this), true); if(this._needsAssetReplacement()) { diff --git a/src/epubcfi.js b/src/epubcfi.js index 723be20..c8e7596 100644 --- a/src/epubcfi.js +++ b/src/epubcfi.js @@ -39,8 +39,13 @@ EPUBJS.EpubCFI.prototype.generatePathComponent = function(steps) { EPUBJS.EpubCFI.prototype.generateCfiFromElement = function(element, chapter) { var steps = this.pathTo(element); var path = this.generatePathComponent(steps); - - return "epubcfi(" + chapter + "!" + path + "/1:0)"; + if(!path.length) { + // Start of Chapter + return "epubcfi(" + chapter + ")"; + } else { + // First Text Node + return "epubcfi(" + chapter + "!" + path + "/1:0)"; + } }; EPUBJS.EpubCFI.prototype.pathTo = function(node) { @@ -96,18 +101,18 @@ EPUBJS.EpubCFI.prototype.parse = function(cfiStr) { end, text; + cfi.str = cfiStr; + if(cfiStr.indexOf("epubcfi(") === 0) { // Remove intial epubcfi( and ending ) cfiStr = cfiStr.slice(8, cfiStr.length-1); } - - chapterComponent = this.getChapterComponent(cfiStr); - pathComponent = this.getPathComponent(cfiStr); + pathComponent = this.getPathComponent(cfiStr) || ''; charecterOffsetComponent = this.getCharecterOffsetComponent(cfiStr); // Make sure this is a valid cfi or return - if(!chapterComponent.length || !pathComponent.length) { + if(!chapterComponent) { return {spinePos: -1}; } @@ -181,18 +186,20 @@ EPUBJS.EpubCFI.prototype.getElement = function(cfi, _doc) { cfi = this.parse(cfi); } - sections = cfi.steps; + sections = cfi.steps.slice(0); // Clone steps array while(sections && sections.length > 0) { part = sections.shift(); - // Wrap text elements in a span and return that new element if(part.type === "text") { text = element.childNodes[part.index]; element = doc.createElement('span'); element.id = "EPUBJS-CFI-MARKER:"+ EPUBJS.core.uuid(); - + element.classList.add("EPUBJS-CFI-MARKER"); + if(cfi.characterOffset) { + // TODO: replace with text.splitText(cfi.characterOffset) + // https://developer.mozilla.org/en-US/docs/Web/API/Text.splitText textBegin = doc.createTextNode(text.textContent.slice(0, cfi.characterOffset)); textEnd = doc.createTextNode(text.textContent.slice(cfi.characterOffset)); text.parentNode.insertBefore(textEnd, text); @@ -200,20 +207,32 @@ EPUBJS.EpubCFI.prototype.getElement = function(cfi, _doc) { text.parentNode.insertBefore(textBegin, element); text.parentNode.removeChild(text); } else { - text.parentNode.insertBefore(element, text); + // If this is the first text node, just return the orginal element + if(part.index === 0){ + element = text.parentNode; + } else { + text.parentNode.insertBefore(element, text); + } } // sort cut to find element by id } else if(part.id){ element = doc.getElementById(part.id); // find element in parent }else{ - if(!children) console.error("No Kids", element); + // if(!children.length) return console.error("No Kids", element); element = children[part.index]; } - - if(!element) console.error("No Element For", part, cfi); + if(!element) return console.error("No Element For", part, cfi, part.index, children, children[part.index]); children = Array.prototype.slice.call(element.children); + // Remove EPUBJS-CFI-MARKER elements + children.forEach(function(child){ + if(child.classList.contains("EPUBJS-CFI-MARKER")){ + var index = children.indexOf(child); + children.splice(index, 1); + } + }); + // console.log(element, children) } return element; diff --git a/src/layout.js b/src/layout.js index 3e263d3..d629da8 100644 --- a/src/layout.js +++ b/src/layout.js @@ -44,7 +44,7 @@ EPUBJS.Layout.Reflowable.prototype.calculatePages = function() { this.documentElement.style.width = "auto"; //-- reset width for calculations totalWidth = this.documentElement.scrollWidth; displayedPages = Math.round(totalWidth / this.spreadWidth); -// console.log(totalWidth, this.spreadWidth) + return { displayedPages : displayedPages, pageCount : displayedPages diff --git a/src/pagination.js b/src/pagination.js index 0a6078c..c0f02e9 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -1,9 +1,10 @@ EPUBJS.Pagination = function(pageList) { - this.pageList = pageList; this.pages = []; this.locations = []; - this.epubcfi = new EPUBJS.EpubCFI(); + if(pageList && pageList.length) { + this.process(pageList); + } }; EPUBJS.Pagination.prototype.process = function(pageList){ @@ -11,14 +12,15 @@ EPUBJS.Pagination.prototype.process = function(pageList){ this.pages.push(item.page); this.locations.push(item.cfi); }, this); - + + this.pageList = pageList; this.firstPage = parseInt(this.pages[0]); this.lastPage = parseInt(this.pages[this.pages.length-1]); this.totalPages = this.lastPage - this.firstPage; }; EPUBJS.Pagination.prototype.pageFromCfi = function(cfi){ - var pg; + var pg = -1; // check if the cfi is in the location list var index = this.locations.indexOf(cfi); if(index != -1 && index < (this.pages.length-1) ) { @@ -32,12 +34,11 @@ EPUBJS.Pagination.prototype.pageFromCfi = function(cfi){ // Add the new page in so that the locations and page array match up this.pages.splice(index, 0, pg); } - return pg; }; EPUBJS.Pagination.prototype.cfiFromPage = function(pg){ - var cfi; + var cfi = -1; // check that pg is an int if(typeof pg != "number"){ pg = parseInt(pg); @@ -73,10 +74,10 @@ EPUBJS.Pagination.prototype.percentageFromCfi = function(cfi){ // TODO: move these EPUBJS.Book.prototype.gotoPage = function(pg){ var cfi = this.pagination.cfiFromPage(pg); - this.gotoCfi(cfi); + return this.gotoCfi(cfi); }; EPUBJS.Book.prototype.gotoPercentage = function(percent){ var pg = this.pagination.pageFromPercentage(percent); - this.gotoCfi(pg); + return this.gotoCfi(pg); }; diff --git a/src/render_iframe.js b/src/render_iframe.js index d25625b..0a02c27 100644 --- a/src/render_iframe.js +++ b/src/render_iframe.js @@ -14,7 +14,11 @@ EPUBJS.Render.Iframe.prototype.create = function(){ this.iframe = document.createElement('iframe'); this.iframe.id = "epubjs-iframe:" + EPUBJS.core.uuid(); this.iframe.scrolling = "no"; + this.iframe.seamless = "seamless"; + // Back up if seamless isn't supported + this.iframe.style.border = "none"; + this.iframe.addEventListener("load", this.loaded.bind(this), false); return this.iframe; }; @@ -27,9 +31,11 @@ EPUBJS.Render.Iframe.prototype.load = function(url){ var render = this, deferred = new RSVP.defer(); - this.leftPos = 0; this.iframe.src = url; - + + // Reset the scroll position + render.leftPos = 0; + if(this.window) { this.unload(); } @@ -43,12 +49,12 @@ EPUBJS.Render.Iframe.prototype.load = function(url){ render.window = render.iframe.contentWindow; render.window.addEventListener("resize", render.resized.bind(render), false); - + //-- Clear Margins if(render.bodyEl) { render.bodyEl.style.margin = "0"; } - + deferred.resolve(render.docEl); }; @@ -62,6 +68,12 @@ EPUBJS.Render.Iframe.prototype.load = function(url){ return deferred.promise; }; + +EPUBJS.Render.Iframe.prototype.loaded = function(){ + var url = this.iframe.contentWindow.location.href; + this.trigger("render:loaded", url); +}; + // Resize the iframe to the given width and height EPUBJS.Render.Iframe.prototype.resize = function(width, height){ var iframeBox; @@ -159,13 +171,15 @@ EPUBJS.Render.Iframe.prototype.getBaseElement = function(){ // Checks if an element is on the screen EPUBJS.Render.Iframe.prototype.isElementVisible = function(el){ var rect; + var left; if(el && typeof el.getBoundingClientRect === 'function'){ rect = el.getBoundingClientRect(); + left = rect.left; //+ rect.width; if( rect.width !== 0 && rect.height !== 0 && // Element not visible - rect.left >= 0 && - rect.left < this.pageWidth ) { + left >= 0 && + left < this.pageWidth ) { return true; } } @@ -185,4 +199,7 @@ EPUBJS.Render.Iframe.prototype.scroll = function(bool){ // Cleanup event listeners EPUBJS.Render.Iframe.prototype.unload = function(){ this.window.removeEventListener("resize", this.resized); -}; \ No newline at end of file +}; + +//-- Enable binding events to Render +RSVP.EventTarget.mixin(EPUBJS.Render.Iframe.prototype); \ No newline at end of file diff --git a/src/renderer.js b/src/renderer.js index 955e7c6..61586b0 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -12,6 +12,9 @@ EPUBJS.Renderer = function(renderMethod) { 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 = {}; @@ -120,9 +123,9 @@ EPUBJS.Renderer.prototype.load = function(url){ this.visible(false); - loaded = this.render.load(url); + render = this.render.load(url); - loaded.then(function(contents) { + render.then(function(contents) { var formated; this.contents = contents; @@ -136,6 +139,7 @@ EPUBJS.Renderer.prototype.load = function(url){ this.render.window.addEventListener("resize", this.resized, false); } + this.addEventListeners(); this.addSelectionListeners(); @@ -161,6 +165,13 @@ EPUBJS.Renderer.prototype.load = function(url){ 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. @@ -189,8 +200,7 @@ EPUBJS.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){ settings[property] = value; } }); - - return settings; + return settings; }; /** @@ -251,7 +261,7 @@ EPUBJS.Renderer.prototype.reformat = function(){ pages = renderer.layout.calculatePages(); renderer.updatePages(pages); - + // Give the css styles time to update clearTimeout(this.timeoutTillCfi); this.timeoutTillCfi = setTimeout(function(){ @@ -322,7 +332,6 @@ EPUBJS.Renderer.prototype.page = function(pg){ this.currentLocationCfi = this.getPageCfi(); this.trigger("renderer:locationChanged", this.currentLocationCfi); - return true; } //-- Return false if page is greater than the total @@ -375,7 +384,20 @@ EPUBJS.Renderer.prototype.section = function(fragment){ }; -// Walk the node tree from an element to the root +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) { var r, children, leng, startNode = node, @@ -387,7 +409,7 @@ EPUBJS.Renderer.prototype.walk = function(node) { while(!r && stack.length) { node = stack.shift(); - if( this.render.isElementVisible(node) ) { + if( this.render.isElementVisible(node) && this.firstElementisTextNode(node)) { r = node; } @@ -398,8 +420,8 @@ EPUBJS.Renderer.prototype.walk = function(node) { } else { return r; } - for (var i = 0; i < leng; i++) { - if(children[i] != prevNode) stack.push(children[i]); + for (var i = leng-1; i >= 0; i--) { + if(children[i] != prevNode) stack.unshift(children[i]); } } @@ -437,6 +459,7 @@ EPUBJS.Renderer.prototype.gotoCfi = function(cfi){ } element = this.epubcfi.getElement(cfi, this.doc); + el = element; this.pageByElement(element); }; @@ -669,4 +692,4 @@ EPUBJS.Renderer.prototype.replaceWithStored = function(query, attr, func, callba }; //-- Enable binding events to Renderer -RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype); +RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype); \ No newline at end of file diff --git a/src/replace.js b/src/replace.js index 2e87c29..afdbac0 100644 --- a/src/replace.js +++ b/src/replace.js @@ -3,6 +3,7 @@ EPUBJS.replace = {}; //-- Replaces the relative links within the book to use our internal page changer EPUBJS.replace.hrefs = function(callback, renderer){ + var book = this; var replacments = function(link, done){ var href = link.getAttribute("href"), relative = href.search("://"), @@ -15,7 +16,7 @@ EPUBJS.replace.hrefs = function(callback, renderer){ }else{ link.onclick = function(){ - renderer.book.goto(href); + book.goto(href); return false; }; diff --git a/tests/epubcfi.js b/tests/epubcfi.js new file mode 100644 index 0000000..ba8bf92 --- /dev/null +++ b/tests/epubcfi.js @@ -0,0 +1,35 @@ +// /demo/moby-dick/OPS/chapter_006.xhtml +// epubcfi(/6/24[xchapter_006]!4/2/14/1:0) + +module('EPUB CFI'); + +asyncTest("Renderer Updates to new CFI", 1, function() { + var book = ePub('/demo/moby-dick/', { width: 400, height: 600 }); + + var render = book.renderTo("qunit-fixture"); + + var result = function(){ + var displayed = book.gotoCfi("epubcfi(/6/24[xchapter_006]!4/2/14/1:0)"); + displayed.then(function(){ + equal( book.getCurrentLocationCfi(), "epubcfi(/6/24[xchapter_006]!4/2/14/1:0)", "Location is correct" ); + start(); + }); + }; + + render.then(result); +}); + +// asyncTest("Find Element from cfi", 1, function() { +// var book = ePub('/demo/moby-dick/', { width: 400, height: 600 }); +// +// var render = book.renderTo("qunit-fixture"); +// +// var result = function(){ +// var d = book.gotoCfi("epubcfi(/6/24[xchapter_006]!4/2/14/1:0)"); +// console.log(d, book.getCurrentLocationCfi()) +// // equal( pg.page, 755, "Page has been parsed" ); +// start(); +// }; +// +// render.then(result); +// }); \ No newline at end of file diff --git a/tests/index.html b/tests/index.html index 34b3a8c..bd32925 100644 --- a/tests/index.html +++ b/tests/index.html @@ -50,6 +50,7 @@ + diff --git a/tests/pagination.js b/tests/pagination.js index d17bbd8..2593641 100644 --- a/tests/pagination.js +++ b/tests/pagination.js @@ -76,7 +76,7 @@ test("Get Percentage from a cfi present in the pageList", 1, function() { test("Get Percentage from a cfi NOT present in the pageList", 1, function() { var pagination = new EPUBJS.Pagination(mockPageList); var pg = pagination.percentageFromCfi("epubcfi(/6/4[ct]!/4/2[d10e42]/20/12/1:0)"); - equal( pg, .17, "Page is found, and correct presentage reported" ); + equal( pg, 0.167, "Page is found, and correct presentage reported" ); }); asyncTest("Generate PageList", 4, function() { @@ -95,9 +95,49 @@ asyncTest("Generate PageList", 4, function() { }); book.pageListReady.then(function(pageList){ - equal(pageList.length, 885, "PageList has been generated"); + equal(pageList.length, 888, "PageList has been generated"); // fixture seems to be removed by the time this is run // equal($("#qunit-fixture frame").length, 1, "Hidden Element Removed"); start(); }); +}); + + +asyncTest("Load a pageList", 2, function() { + var book = ePub('../books/moby-dick/', { width: 1076, height: 588 }); + + var render = book.renderTo("qunit-fixture"); + + EPUBJS.core.request("../examples/page_list.json").then(function(storedPageList){ + pageList = storedPageList; + book.loadPagination(pageList); + }); + + book.pageListReady.then(function(pageList){ + equal(pageList.length, 394, "PageList has been generated"); + equal(book.pagination.lastPage, 394, "All pages present") + start(); + }); +}); + + +asyncTest("gotoPage after generating page list", 2, function() { + var book = ePub('../demo/moby-dick/', { width: 1076, height: 588 }); + + var render = book.renderTo("qunit-fixture"); + + book.generatePagination(); + + book.pageListReady.then(function(pageList){ + // console.log(JSON.stringify(pageList)); + var changed = book.gotoPage(38); + changed.then(function(){ + equal(book.getCurrentLocationCfi(), "epubcfi(/6/24[xchapter_006]!4/2/14/1:0)", "Page changed"); + start(); + }); + + start(); + equal(pageList.length, 394, "PageList has been generated"); + stop(); + }); }); \ No newline at end of file diff --git a/tests/render.js b/tests/render.js index 6d68f72..225534d 100644 --- a/tests/render.js +++ b/tests/render.js @@ -63,10 +63,10 @@ asyncTest("Go to chapter 1 and advance to next page", 4, function() { $body = $iframe.contents().find("body"); Book.nextPage(); - equal( $body.scrollLeft(), 454, "on page 2"); + equal( $body.scrollLeft(), 450, "on page 2"); Book.nextPage(); - equal( $body.scrollLeft(), 908, "on page 3"); + equal( $body.scrollLeft(), 900, "on page 3"); }); @@ -149,11 +149,11 @@ asyncTest("Display end of chapter 20 and go to prev page", 3, function() { $body = $iframe.contents().find("body"); Book.prevPage(); - equal( $body.scrollLeft(), 1362, "on last page"); + equal( $body.scrollLeft(), 1350, "on last page"); Book.prevPage(); - equal( $body.scrollLeft(), 908, "on second to last page "); + equal( $body.scrollLeft(), 900, "on second to last page "); }); @@ -206,9 +206,9 @@ asyncTest("Switch Spreads to Single", 3, function() { var result = function(){ equal( Book.renderer.spreads, true, "Use Spreads"); - Book.useSpreads(false); + Book.forceSingle(true); equal( Book.renderer.spreads, false, "Don't Use Spreads"); - equal( Book.renderer.contents.style[EPUBJS.core.prefixed('columnWidth')], "352px", "Don't Use Spreads"); + equal( Book.renderer.contents.style[EPUBJS.core.prefixed('columnWidth')], "350px", "Don't Use Spreads"); start(); };