futurepress / epub.js

Enhanced eBooks in the browser.
http://futurepress.org
Other
6.48k stars 1.11k forks source link

Getting current page and total number of pages #744

Open vblazenka opened 6 years ago

vblazenka commented 6 years ago

Hi, I would like to show something like this (in my reader footer): Page 5 - 323. This is basically this: "Page " + currentPage + " - " + totalPages

But I didn't find a way to get currentPage and totalPages number.

For currentPage I tried:

this.rendition.on('relocated', (location) => {
...
this.book.locations.locationFromCfi(location.start.cfi);
...
});

But I get strange numbers like (in order): 0, 1, 2, 4, 6, 8, 11...

And I'm not even sure how to get totalPages number.

Can anyone help? I would like to find solution for both cases and add them to epub documentation.

EDIT: I use v0.3, I found that v0.2 has generatePagination method but I want to use v0.3

vblazenka commented 6 years ago

Hey, @fchasen. Thank you for this cool library you and other people created.

I was wondering if you could give me some tips for regarding my question? I would love to help back.

Thank you!

jlag34 commented 6 years ago

@wedranb if you look at the wiki under Updating to v0.3 from v0.2 you find that they have a new method:

generatePagination() was far too resource intensive, as it rendered every page. It has been replaced by book.locations.generate(600) which will create a CFI for every X characters in the book.

From my understanding depending on the argument you pass (150 being the default) it goes through the book and marks where it is every X characters. You get back a big array of pages. I don't find this very helpful though because it's not actual pages, just a rough estimate. Using this method you could skip a page if your number is too high or count a page twice if too low. It seems that when you use rendition.currentLocation() it has an end and start. In those is the list of pages in that chapter but that can be wrong based off of your screen size. All in all, there seems to be no easy way to do it.

mikkelvp commented 6 years ago

Users want total page number. Support for it would be great. After the current section is rendered, rest of book could be initialised to calculate pages. book.locations.generate currently doesn't help.

fchasen commented 6 years ago

Getting the page for a currently rendered chapter is simple enough, but rendering the entire book out in each users browser to get page numbers just wasn't practical.

Would be amazing to render out the entire book server side and save the pages as an epub page list. With Puppeteer it should not be too hard to setup.

pgaskin commented 6 years ago

How about showing the user a page number in the format chapter.page (e.g. 1.1, 1.1, 1.2, 1.3, 1.4, 1.5, 2.1, 2.3, etc)? It would still increment predictably and be easy to read. It would also be a lot faster and easier to generate than full pagination, but still better fit your use case than locations.

mikkelvp commented 6 years ago

@geek1011 You can do that with numbers from rendition.currentLocation()

I've made an extra offscreen rendering of the book. I use that to load one section at a time and get page counts. Takes a while but it's no problem since it's all in the background after the book has loaded. Will show a spinner or similar where page counts are shown while loading them.

jlag34 commented 6 years ago

@geek1011 I think the basic user (who is just reading with the epub, never coded anything) will be confused by that page numbering system. Wouldn't want to have to tell every user why their page numbers are weird.

@fchasen thanks for that. I'll give puppeteer a shot and let you know!

askilondz commented 6 years ago

book.locations.generate returns a promise containing an array of all the CFI locations. Correct me if I am wrong but if these locations are all the pages then we can just get the length of the array.

        this.book.ready.then((book) => {
            return this.book.locations.generate();
        }).then(locations => {
            console.log("Total Pages?: ", locations.length);
        });
mikkelvp commented 6 years ago

@askilondz You are wrong. It generates cfi ranges for every x (default 150) characters which has nothing to do with pages.

askilondz commented 6 years ago

@mikkelvp :-) thanks for the clarification.

fgilio commented 6 years ago

Hi guys! Has anyone found a practical, and hopefully accurate, solution to this?

So far the approaches I'm considering are: A) @fchasen solution sounds nice, but doesn't the number of pages depend also on screen and font size? B) Use book.locations.generate, though it has nothing to do with pages

Also, what about fixed-layout epubs?

mikkelvp commented 6 years ago

@fgilio I've described my solution earlier in this issue. A) Yes, if you have applied styling you will need to wait for browser to render with that styling before getting page count for a section.

fgilio commented 6 years ago

Hi @mikkelvp That's what I was afraid of. There seems like there is no way of preprocessing this server side only once. It would need to be generated either client side, or with a, probably overly complex, server side solution that takes into account this variables (screen size and font size, and maybe others). The server side solution could also do it on demand and store the results for the next time the epub and variables match. Or maybe I'm just overly complicating this, but this is when a pdf sounds like something super simple 😅

askilondz commented 6 years ago

@mikkelvp Could you show your code for how you're rendering each section for the offscreen rendering you are doing? I like this approach for getting the page count. I'm looping through each spine item and attempting to render each item/section item.render() but having some issues would be curious to see your solution. Thanks!

ghost commented 6 years ago

@fchasen I want to show more sections together, e.g. if the page is having an image, breaking it into sections shows part of face then neck then chest and so on, and which does not make any sense. Who can help?

scott-engemann commented 5 years ago

Here's the best solution that I have come up with for getting page number and total pages based on setting the number of characters per page:

// Initialize the book
let bookUri = "https://s3.amazonaws.com/moby-dick/moby-dick.epub";
let book = ePub(bookUri, {});
let rendition = book.renderTo('epubContainer', {
    flow: 'paginated',
    manager: 'continuous'
    spread: 'always'
    width: "100% - 106px",
    height: this.calculateReaderHeight()
});

// Display the book
let displayed = rendition.display(window.location.hash.substr(1) || undefined);
displayed.then(function() {
    console.log('rendition.currentLocation():', rendition.currentLocation());
});

// Generate location and pagination
book.ready.then(function() {
    const stored = localStorage.getItem(book.key() + '-locations');
    console.log('metadata:', book.package.metadata);
    if (stored) {
        return book.locations.load(stored);
    } else {
        return book.locations.generate(1024); // Generates CFI for every X characters (Characters per/page)
    }
}).then(function(location) { // This promise will take a little while to return (About 20 seconds or so for Moby Dick)
    localStorage.setItem(book.key() + '-locations', book.locations.save());
});

// When navigating to the next/previous page
rendition.on('relocated', function(locations) {
    progress = book.locations.percentageFromCfi(locations.start.cfi);
    console.log('Progress:', progress); // The % of how far along in the book you are
    console.log('Current Page:', book.locations.locationFromCfi(locations.start.cfi);
    console.log('Total Pages:', book.locations.total);
});

EPubJS will use these generated epubCFI's for a "start" and an "end" for each page. The bigger the number you set in the book.locations.generate() method, the less number of pages you have. This is also why I'm logging the book.package.metadata so that I can determine if the publisher has set a "characters per page" so I can use that value. I'm still early on in my epub.js development but have learned quite a bit over the past week.

I hope this can help someone.

vblazenka commented 5 years ago

@sengemann how accurate is this? What if the user changes font size or screen size?

scott-engemann commented 5 years ago

It updates accordingly when the relocated event is triggered. I will have to see how to manually trigger that event for the actual values to change.

yh54321 commented 5 years ago

It updates accordingly when the relocated event is triggered. I will have to see how to manually trigger that event for the actual values to change.

As far as your understanding, if I call rendition.currentLocation() when on a given page, will the returned start and end CFIs be the actual start and end CFIs of the page 100% of the time? Or are these just estimates, so that if I go to one of these start CFIs, it may not bring me to the correct page...

If anyone else has an answer to this it will likewise be appreciated.

ezequiel9 commented 5 years ago

The documentation of this library is really bad. Seems to be very good project but I'm losing a lot of time guessing how it works :(((((

MrXCQ commented 5 years ago

Hi,How to get current page and total number of pages? Does anybody have a solution? PLease 🥺

vblazenka commented 5 years ago

@MrXCQ You can't. :) In this thread you can find possible solutions but nothing is accurate.

scott-engemann commented 5 years ago

It updates accordingly when the relocated event is triggered. I will have to see how to manually trigger that event for the actual values to change.

As far as your understanding, if I call rendition.currentLocation() when on a given page, will the returned start and end CFIs be the actual start and end CFIs of the page 100% of the time? Or are these just estimates, so that if I go to one of these start CFIs, it may not bring me to the correct page...

If anyone else has an answer to this it will likewise be appreciated.

The only time that the method rendition.currentLocation() has not been returning the correct location was immediately after adjusting the font-size via the themes() method. I have a bug created for that here: https://github.com/futurepress/epub.js/issues/982

hesampour commented 4 years ago

@sengemann would you please give us the calculateReaderHeight() function?

scott-engemann commented 4 years ago

@hesampour, it's pretty simple once you know what your variables are:

calculateReaderHeight() {
  let h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
  // On the next line you need to factor in the height of any of your UI elements.
  // You could just take the  values if they are static or use jQuery to get the heights dynamically (that would be the preferred method)
  h = h - 50 - 68 - 44; // 50: Header Height, 68: Footer Height, 44: 22px * 2 top/bottom margin
  return h;
},
jiangxiaopeng commented 4 years ago

The documentation of this library is really bad. Seems to be very good project but I'm losing a lot of time guessing how it works :(((((

thats right

hrkazemi commented 4 years ago

does anyone have a solution for pagination?

sbrighiu commented 4 years ago

I'm messing around with the react native implementation, and adjusting it to my needs here https://github.com/sbrighiu/epubjs-rn. @hrkazemi you can look in the repo for pagingEnabled={true}.

I've also found the documentation for epub.js http://epubjs.org/documentation/0.3/, if newcomers don't find it immediately

Would be really nice to be able to get number of pages and current page, but as my content varies from device size to device size and from epub to epub, I don't see a reliable way of doing it :(

sbrighiu commented 4 years ago

What about the completion? Like a percentage? 61%

scott-engemann commented 4 years ago

@sbrighiu and @MrXCQ This is the best solution that I have came up with to get the percentage, current page, page range and total pages: https://github.com/futurepress/epub.js/issues/744#issuecomment-492300092

There are a few requirements for this to work:

hrkazemi commented 4 years ago

What about the completion? Like a percentage? 61% @sbrighiu I did use the mupdf library instead. it's work for both epub & pdf (and great if you are doing native but has poor document).

JoaoVictorSarzi commented 3 years ago

@fchasen help me please... I have to different .epub, but crafted the same way... i dont know why one of then dont generate the locations, get the spine and everything but location returns -1 only. The promise dont complete and dont get in on .then(). Promisse sshow this on every part: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.o (:1:83)]

OzkanAbdullahoglu commented 3 years ago

seems like this one is still open. While I was trying to figure things out, I just found a solution that looks like enough for my current problem. In the Rendition object, we have location as a static member. In location, there are start and end objects so in these objects we have an index ( which I assume right now as a section) and in displayed there is a page and total numbers which belong to this index.

You can check the docs again for the detailed structure.

Also here below I inserted a screenshot for a better understanding.

Meanwhile, If anyone got a better idea please let me know.

Screen Shot 2021-05-18 at 03 10 50
RatulSikder97 commented 2 years ago

seems like this one is still open. While I was trying to figure things out, I just found a solution that looks like enough for my current problem. In the Rendition object, we have location as a static member. In location, there are start and end objects so in these objects we have an index ( which I assume right now as a section) and in displayed there is a page and total numbers which belong to this index.

You can check the docs again for the detailed structure.

Also here below I inserted a screenshot for a better understanding.

Meanwhile, If anyone got a better idea please let me know.

Screen Shot 2021-05-18 at 03 10 50

Here, you only get the Total page and current page for the current chapter, not the overall. 😥

ghost commented 2 years ago

this is not working

elliotdickison commented 1 year ago

Some thoughts after beating my head against this problem for a few hours:

Page numbers are a lost cause

IMO "page number" is not a particularly helpful concept when it comes to ebooks. An "accurate" page number that increments or decrements by exactly 1 every time the user moves backwards or forwards by 1 page must be calculated on the client. There is no way around this - this type of page number is dependent on the exact rendering of the book which is dependent on the client's viewport, the client's interpretation of fonts, the client's interpretation of CSS, etc.

Even if you were willing to deal with this limitation and churn out page numbers on the client, the results are not that helpful to the user. You are showing them an arbitrary page number that won't match up with page numbers generated on another other device or even on the same device if the view is rotated resized. Imagine reading a book, noting that you were on page 3 of 450, rotating your device, and then discovering that you are now on page 2 of 300. This is confusing (and unavoidable).

A better metric

I think total % progress in the book is a much better metric. This can be calculated with locations as demonstrated in some of the responses above and shouldn't change appreciably if the user switches devices, rotates their screen, resizes their browser, etc. For a little more context you could combine this with "X pages left in chapter" which doesn't require any extra calculations (although this number would change if the view is resized/rotated/etc.).

Generating locations server side

@fchasen mentioned using Puppeteer to generate locations on the server. I'm sure this would work, but Puppeteer tends to be overkill. I did a little testing and with some patience it's possible to use jsdom and xhr2 to get epubjs working in Node itself (at least enough to where you can generate locations and extract the book's table of contents).

Hope these ramblings are somewhat helpful.

evanreichard commented 1 year ago

I've been battling with this for a bit so I'm still trying to find better ways to do this as quickly and accurately as possible. The best I've come up with:

Since we have access to:

We can estimate the current and total pages:

TotalLocations / TotalPages = SectionLocations / SectionPages
Total Pages = (TotalLocations * SectionPages) / SectionLocations;

The implementation:

async function estimatePages() {
  if (book.locations.total == 0) await book.locations.generate(1024);

  let currentLocation = rendition.currentLocation();
  let sectionIndex = currentLocation.start.index;
  let sectionPages = currentLocation.start.displayed.total;
  let sectionBaseCFI = book.spine.get(sectionIndex).cfiBase;

  let sectionStartLocation = book.locations._locations.findIndex((item) =>
    item.startsWith("epubcfi(" + sectionBaseCFI)
  );
  let sectionLocations = book.locations._locations.filter((item) =>
    item.startsWith("epubcfi(" + sectionBaseCFI)
  ).length;

  let totalLocations = book.locations.total;
  let estPages = (totalLocations * sectionPages) / sectionLocations;
  let estSectionStartPage =
    estPages * (sectionStartLocation / totalLocations);
  let estCurrentPage =
    estSectionStartPage + currentLocation.start.displayed.page;
  return {
    currentPage: Math.round(estCurrentPage),
    totalPages: Math.round(estPages),
  };
}

Benefits / Notes:

It's not perfect, but it's the best I could come up with.

vedmant commented 10 months ago

@evanreichard This doesn't work, I'm testing on my book and on different chapters it shows completely different numbers for total and current pages.

evanreichard commented 10 months ago

@vedmant Yeah there's some wonkyness when changing between chapters. I ended up abandoning total page count and reverting to total percentage, and the current / total page counts for the current chapter only.

chrisovato commented 6 months ago

Any updates on this case?

scott-engemann commented 6 months ago

Any updates on this case?

@chrisovato - Check out my method and see if it works for you: https://github.com/futurepress/epub.js/issues/744#issuecomment-492300092