Giter VIP home page Giter VIP logo

Comments (11)

panurge-ws avatar panurge-ws commented on July 23, 2024

Hello,
To build a slider of pages, I think we have to know the number of pages of the book, in a printing-like way (x of y).
I have written a little extension of the Renderer class to manage that kind of thing. It can be a little bit expensive in terms of resources because it needs to load ALL the contents of the book and to format/layout them.
Anyway, it is actually (and probably) the only way to reach it.
Inside this extension I put a couple of methods to get and set the percentage of reading, so the UI builder cna use them with a slider bar.

I copy here, this long extension.
@fchasen: I followed the coding style of the Renderer class, but if you want to edit it, don't hesitate to do it.
I extended the Renderer, because it seems the fastest way, but probably it could be rewritten as a new Class / Plugin, passing the "book" object to deal with.
I hope it can be useful.

/* 
 * 
 * It performs a calculation of printing-like pages number (x of y), loading silently 
 * and progressly all the contents of the book and formatting them to fit the actual render dimensions.
 * You should call this function after the render is displayed and has formatted the layout (e.g. the first time of "renderer:chapterDisplayed").
 * You can listen from it two events:
 * "renderer:pagesNumChanged" -> dispatched after the operation is completed (returns the totalPages)
 * "renderer:pagesNumProgress" -> dispatched during the operation (returns an object with pagesLoaded and pagesTotal)
 * 
 * After having called this, you can use the utils/display methods to retrieve the formatted pages number, reading percentage etc.
 * See below.
 * 
 */
EPUBJS.Renderer.prototype.calculateNumberOfPages = function(avoidCalcOnResize){

    console.log("EPUBJS.Renderer.prototype.calculateNumberOfPages");

    // destroy any previous operations
    if (this.tempIFrame){
        document.body.removeChild(render.tempIFrame);
        this.tempIFrame = null;
    }

    // FLAG STATUS
    this.isScanningComplete = false;
    // the total pages of the book
    this.totalPages = 0;
    // loading index
    this.currPageScanned = -1;
    // object to store info
    this.chaptersPages = {};
    // timeout for delay in resize
    this.resizeTimeOutID = -1;
    //this.book.spine = [];

    // add an event listener on resize, so we can compute again
    if (!this.resizeEventAdded && avoidCalcOnResize !== true){
        this.on("renderer:resized",this.onResizeNumPages,this);
        this.resizeEventAdded = true;
    }

    // set as instance of Render so we can resolve it after the loading
    this.promiseCalcPages = new RSVP.Promise();

    // check if we have a copy in localStorage that fits the actual dimensions (in mobile device it's quite usual) so we can use it, without performing again the operation
    var savedData = this.loadPagesNum();
    if (savedData != null && !this.book.settings.reload){ // reload on settings.reload
        this.isScanningComplete = true;
        this.chaptersPages = savedData.chaptersPages;
        this.totalPages = savedData.totalPages;
        this.promiseCalcPages.resolve(this.totalPages);
        this.lastPercent = savedData.lastPercent;
        console.log("EPUBJS.Renderer.prototype.calculateNumberOfPages->using localStorage:",this.totalPages);
        this.trigger("renderer:pagesNumChanged",this.totalPages);
        this.book.trigger("renderer:pagesNumChanged",this.totalPages);
        return this.promiseCalcPages;
    }

    this.calcNextPage();

    return this.promiseCalcPages;
}

/* Return the page num for the first page of a chapter */ 
EPUBJS.Renderer.prototype.firstPageOfChapter = function(chapter){
    if (!this.isScanningComplete) return -1;
    console.log("firstPageOfChapter",this.chaptersPages[chapter.spinePos].firstPage, this.chapterPos);
    return this.chaptersPages[chapter.spinePos].firstPage;
}
/* Return the current page */ 
EPUBJS.Renderer.prototype.getCurrentPage = function(){
    if (!this.isScanningComplete) return -1;
    return this.firstPageOfChapter(this.currentChapter) + this.chapterPos;

}
/* Return a formated page num like "1 / 120".
 * You can pass a separator to format the result and/or a loading indicator in HTML
 * You should call this functon everytime the page is changed ("renderer:pageChanged" and "renderer:pagesNumChanged")
 */ 
EPUBJS.Renderer.prototype.getCurrentPageOfTotPages = function(separator,loadingIndicator){
    loadingIndicator || (loadingIndicator = "...");
    if (!this.isScanningComplete) return loadingIndicator;

    separator || (separator = " / ");
    return  this.getCurrentPage() + separator + this.totalPages;
}

/* Return a percent of reading (from 0 to 1) 
 * This could be usefull to build a slider.
 */
EPUBJS.Renderer.prototype.getReadingPercentage = function(){
    if (!this.isScanningComplete) return null;
    // to return a "real" 0 to 1 rappresentation we should force this to 0 even if, logically, the first page should be considered as read when displayed, so > 0.
    if (this.getCurrentPage() == 1) return 0;
    return  this.getCurrentPage() / this.totalPages;
}
/*
 * Go to page num of the book
 */
EPUBJS.Renderer.prototype.gotoPageNum = function(pageNum){
    if (!this.isScanningComplete) return false;
    var render = this;
    for (var spinePos in this.chaptersPages) {
        var chapter = this.chaptersPages[spinePos]; 
        if (pageNum > chapter.firstPage  && pageNum <= (chapter.firstPage+chapter.pages))
        {
            var chapterPos = pageNum - chapter.firstPage;
            if (this.book.spinePos != spinePos){

                this.book.spinePos = Number(spinePos);
                this.book.displayChapter(this.book.spinePos).then(function(chapter){
                    render.page(chapterPos);
                })
            }
            else{
                this.page(chapterPos);
            }
            return true
        }
    };

    return false;
}
/*
 * Go to percentage of reading.
 * percent => from 0 to 1;
 * (usefull for slider bars)
 */
EPUBJS.Renderer.prototype.gotoPercent = function(percent){
    var currPage = Math.round(this.totalPages*percent);
    currPage = currPage == 0 ? 1 : currPage;
    //console.log("EPUBJS.Renderer.prototype.gotoPercent",percent,currPage);
    this.gotoPageNum(currPage);

}


// UTILS // INTERNAL //
EPUBJS.Renderer.prototype.onResizeNumPages = function(e){
    console.log("EPUBJS.Renderer.prototype.onResizeNumPages");
    clearTimeout(this.resizeTimeOutID);
    // because the operation requires a lot of resources, we wait long enough before recalculating, being sure of the new dimensions
    // Anyway flag as scanning
    this.isScanningComplete = false;
    this.resizeTimeOutID = setTimeout(this.recalcPages,2500,this); // pass the scope
}

EPUBJS.Renderer.prototype.recalcPages = function(render){
    render.calculateNumberOfPages(true);
}

EPUBJS.Renderer.prototype.calcNextPage = function(){

    if (this.book.spine[this.currPageScanned+1]){

        this.currPageScanned++;

        this.tempIFrame = document.createElement('iframe');

        document.body.appendChild(this.tempIFrame);

        this.tempIFrame.src = this.book.spine[this.currPageScanned].href;

        this.tempIFrame.width = this.el.clientWidth;
        this.tempIFrame.height = this.el.clientHeight;
        this.tempIFrame.style.visibility = "hidden";

        var render = this;

        this.tempIFrame.onload = function(){
            //console.log("PagesCalc-scanNext-onLoad");

            var docEl = render.tempIFrame.contentDocument.documentElement;
            var bodyEl = render.tempIFrame.contentDocument.body;

            if(render.book.settings.fixedLayout) { // TODO, test if it works -> copied from Render.fixedLayout  (probably we should use a seprated function to have a the same format)

                docEl.style.width = render.tempIFrame.width;

                //-- Adjust height
                docEl.style.height = "auto";

                //-- Remove columns
                // this.docEl.style[EPUBJS.core.columnWidth] = "auto";

                //-- Scroll
                docEl.style.overflow = "auto";
            }
            else{ // TESTED -> copied from Render.formatSpread (probably we should use a seprated function to have a the same format)

                //-- Clear Margins
                bodyEl.style.margin = "0";

                docEl.style.overflow = "hidden";

                docEl.style.width = render.tempIFrame.width  + "px";

                //-- Adjust height
                docEl.style.height = render.tempIFrame.height  + "px";

                //-- Add columns
                docEl.style[EPUBJS.Renderer.columnAxis] = "horizontal";
                docEl.style[EPUBJS.Renderer.columnGap] = render.gap+"px";
                docEl.style[EPUBJS.Renderer.columnWidth] = render.colWidth+"px";

            }
            // calc pages
            var totalWidth = docEl.scrollWidth;

            var displayedPages = Math.ceil(totalWidth / render.spreadWidth);
            // save with index, so we can retrieve after from spinePos
            render.chaptersPages[render.book.spine[render.currPageScanned].index] = {firstPage:render.totalPages,pages:displayedPages};
            render.totalPages += displayedPages;

            //console.log("scanNext-pages",displayedPages,render.book.spine[render.currPageScanned].href);

            document.body.removeChild(render.tempIFrame);
            render.tempIFrame = null;

            var progEventData = {pagesLoaded:(render.currPageScanned+1),pagesTotal:render.book.spine.length}
            render.trigger("renderer:pagesNumProgress", progEventData);
            render.book.trigger("renderer:pagesNumProgress", progEventData);

            render.calcNextPage();
        }
    }
    else{
        this.isScanningComplete = true;
        this.promiseCalcPages.resolve(this.totalPages);
        this.trigger("renderer:pagesNumChanged",this.totalPages);
        this.book.trigger("renderer:pagesNumChanged",this.totalPages);
        this.savePagesNum();
    }
}


/* save in localStorage */ 
EPUBJS.Renderer.prototype.savePagesNum = function(){
    if (!localStorage || !this.isScanningComplete) return;
    var pagesNumKey = this.book.settings.bookPath + ":pages:" + this.book.settings.version;
    var pagesObject = {width:this.el.clientWidth, height:this.el.clientHeight, totalPages:this.totalPages, chaptersPages:this.chaptersPages, lastPercent:this.getReadingPercentage()};
    localStorage.setItem(pagesNumKey,JSON.stringify(pagesObject))
}
/* load from localStorage */
EPUBJS.Renderer.prototype.loadPagesNum = function(){
    if (!localStorage) return null;

    var pagesNumKey = this.book.settings.bookPath + ":pages:" + this.book.settings.version,
        fromStore = localStorage.getItem(pagesNumKey);

    if(fromStore && fromStore != 'undefined' && fromStore != 'null'){
        var pagesObject = JSON.parse(fromStore);
        // we can use it only if we are at the same dimensions we had calculate before
        if (pagesObject.width == this.el.clientWidth && pagesObject.height ==  this.el.clientHeight){
            return pagesObject;
        }
    }
    return null;
}


from epub.js.

dghag avatar dghag commented on July 23, 2024

Can you please update in this version? So I can download & see the output.

from epub.js.

panurge-ws avatar panurge-ws commented on July 23, 2024

You can copy the code above in a file and embed it in your html page after the epub.js script tag, like a sort of plugin.
Then, when you have a EPUBJS.Book instance with a valid renderer you can call these lines (anyway I recommend you to do it only when you are sure the layout of the book is done).

book.on("renderer:pagesNumChanged", onPageChange);
book.render.calculateNumberOfPages();

function onPageChange()
{
console.log(book.render.getCurrentPageOfTotPages(" / ", "(...)"));
}

from epub.js.

fchasen avatar fchasen commented on July 23, 2024

Sorry I haven't had a chance to review this yet.

We were thinking there might be a way to get close enough to create a slider by just getting the file-size of each chapter and estimating based on that.

from epub.js.

panurge-ws avatar panurge-ws commented on July 23, 2024

Before writing that, I have also managed to implement the file size method, because, for sure, it's considerably less expensive. But I was not able to get an accurate estimation for pages number. With books heavily formatted (a lot of new lines, dialogues etc.) and, moreover, with illustrated books the estimation is quite inaccurate for me to consider that as a good result.
Probably to manage merely the percentage (not the exact pages number), the file size method could reach quite good results (not with illustrated ebooks). Anyway, to simulate a "real" page number (the one the reader is wont to refer), I guess it's not suitable.

from epub.js.

justinHume avatar justinHume commented on July 23, 2024

Another way you could do it would be to load and render the entire epub once, generate a CFI for each "page" (say, the first visible element) and then use the list of generated CFIs as indexes in the slider (or whatever other sort of navigation control).

Of course this is also an approximation, but probably a decent one under the assumption that a single user will likely be reading with the same screen each time. Changing font and style settings, margins, synthetic layout would also affect the approximation, but so it will with any method except for recalculating the number of pages dynamically (which requires the entire EPUB to be loaded in memory, as pointed out).

The other benefit of this method is that you could (I think) generate the CFIs using a headless browser for a variety of different screen sizes/style settings and have them ready to go when the user loads the epub. So, for example, a list of CFIs for a single page layout on an iPhone, iPad, iPad mini etc. It might provide a better estimation than inferring from the size of un-rendered xhtml and would be more performant than having to load the entire epub.

You could also do this without CFIs, but tying your "page" reference to a specific element at least provides some guarantee that you can navigate back to a position familiar to a user. Using CFIs also means that users can share their "page" reference with another user (who has a different screen size and "number of pages") and still be able to point them at about the same content.

from epub.js.

fchasen avatar fchasen commented on July 23, 2024

I suppose there isn't really a way around loading and rendering the whole book to get the page numbers, though I think for most instances a less accurate progress indicator should suffice.

Finding the CFI for each page would be even more expensive then just rendering them, but it would certainly be more useful and I love the idea of being able to pre-populate it for certain screen size / fixed width book.

I'll try to split up the renderer to be able to be more flexible in regards to being "headless" and make some tests to see how quickly we can generate a cfi for every page.

from epub.js.

justinHume avatar justinHume commented on July 23, 2024

Definitely true that it would be more expensive. I should have clarified my assumption, which is that it would only be used to pre-populate it for certain screen sizes. Generating CFIs for every page every time a user opens and epub would, as you said, require loading the whole epub, with CFI generation overhead on top of that.

from epub.js.

panurge-ws avatar panurge-ws commented on July 23, 2024

Regardless of the way you will choose to develop the paging system, be careful that the current implementation of CFI fails to restore the exact position within small resolutions, mostly when there are long paragraphs/elements inside a page/column. In my iPhone, with some ePubs it fails about 1 of 15 times.

from epub.js.

fchasen avatar fchasen commented on July 23, 2024

Closing this for now as much what @panurge-ws commented on was incorporated in in the Generate Pagination method.

However, I think the pagination methods still need much improvement and discussion. For a first step I'm hoping to create a rough pagination method based on file size.

from epub.js.

kewal07 avatar kewal07 commented on July 23, 2024

Hi. I am sorry to post in this closed issue but can anyone please help me implement a paging slider. I tried @panurge-ws way of implementation above but could not get it working. Can someone please guide me.

from epub.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.