gerhardsletten / react-reader

An ePub-reader for React, powered by Epub.js
https://react-reader.metabits.no
Apache License 2.0
721 stars 132 forks source link

[Issue] Only cover page rendered for certain epub, rendering a different epub fixes it #169

Closed kevsjh closed 9 months ago

kevsjh commented 9 months ago

Found a weird behaviour where React-reader only renders cover page for certain epub on first load. However the same epub can be fully rendered if a different epub is rendered previously.

Attaching said epub files in this thread to reproduce https://drive.google.com/drive/folders/1OaP037da7KviYz_7-TmTYLWejhGzH7Gj?usp=drive_link

Steps to reproduce 0.)On page load 1.) select b.epub/c.epub (only renders cover page) 2.) select a.epub (renders full page), navigate to next page 3.) select b.epub/c.epub (this time, it renders full page) 4.) b.epub/c.epub only renders fully if a.epub was loaded previously

Similar behaviour for both bufferArray and BlobURL

Reproducible code in nextjs14 app router

"use client";

import React, { useState } from "react";
import { ReactReader } from "react-reader";

export default function Page() {
  const [location, setLocation] = useState<string | number>(0);
  const [selectedDocs, setSelectedDocs] = useState<File | undefined>(undefined);
  const [blobArrBuffer, setBlobArrBuffer] = useState<ArrayBuffer | undefined>(
    undefined
  );

  const [blobUrl, setBlobUrl] = useState<string | undefined>(undefined);

  return (
    <div className="h-screen w-1/2 pt-24">
      <input
        type="file"
        accept=".epub"
        multiple
        onChange={async (el) => {
          if (el.target.files?.length) {
            const file: Blob = el.target.files[0];
            const selFile: File = el.target.files[0];
            const buf = await file.arrayBuffer();
            setBlobArrBuffer(buf);
            setSelectedDocs(selFile);
            const blob = URL.createObjectURL(selFile);

            setBlobUrl(blob);
          }
        }}
      />
      {blobUrl && (
        <div className="h-full w-full">
          <ReactReader
            url={blobUrl}
            showToc
            location={location}
            locationChanged={(epubcfi: string) => {
              console.log(epubcfi);
              setLocation(epubcfi);
            }}
            epubInitOptions={{
              openAs: "epub",
              // encoding:'base64',
              // replacements:'base64'
            }}
            tocChanged={(e) => console.log(e)}
            epubOptions={{
              // flow: "scrolled",
              // manager: "continuous",
              allowPopups: true,
              allowScriptedContent: true,
            }}
            getRendition={(r) => {
              console.log(r);
            }}
          />
        </div>
      )}
    </div>
  );
}
jordant97 commented 9 months ago

have u found a solution? i have the same problem

kevsjh commented 9 months ago

nope, i did a hackaround before loading any epub, preload a dummy epub, programmatically scroll to next page, and start loading the desired epub

gerhardsletten commented 9 months ago

@jordant97 @kevsjh Think the problem is the "robustness" in how epub.js handle the epub. From what source or editor was the epub made?

If you unzip b.epub and apply this patch:

diff --git a/OEBPS/xhtml/004_contents.xhtml b/OEBPS/xhtml/004_contents.xhtml
index a8d226d..e65e8d2 100644
--- a/OEBPS/xhtml/004_contents.xhtml
+++ b/OEBPS/xhtml/004_contents.xhtml
@@ -9,15 +9,15 @@
 <h2 class="h2_2">CONTENTS</h2>
 <nav epub:type="toc" id="toc">
 <ol class="olnone">
-<li class="hidden_toc"><a href="001_cover.xhtml">Cover</a></li>
-<li class="hidden_toc"><a href="002_title.xhtml">Title Page</a></li>
-<li class="hidden_toc"><a href="004_contents.xhtml">Contents</a></li>
-<li class="topa"><a href="005_chapter001.xhtml"><strong>I</strong> <em>The Lovers</em></a></li>
-<li class="topa"><a href="006_chapter002.xhtml"><strong>II</strong> <em>Husband and Wife</em></a></li>
-<li class="topa"><a href="007_chapter003.xhtml"><strong>III</strong> <em>The Sisters</em></a></li>
-<li class="topa"><a href="008_chapter004.xhtml"><strong>IV</strong> <em>Mother and Child</em></a></li>
-<li class="hidden_toc"><a href="009_about_author.xhtml">About the Author</a></li>
-<li class="hidden_toc"><a href="010_copyright.xhtml">Copyright</a></li>
+<li class="hidden_toc"><a href="xhtml/001_cover.xhtml">Cover</a></li>
+<li class="hidden_toc"><a href="xhtml/002_title.xhtml">Title Page</a></li>
+<li class="hidden_toc"><a href="xhtml/004_contents.xhtml">Contents</a></li>
+<li class="topa"><a href="xhtml/005_chapter001.xhtml"><strong>I</strong> <em>The Lovers</em></a></li>
+<li class="topa"><a href="xhtml/006_chapter002.xhtml"><strong>II</strong> <em>Husband and Wife</em></a></li>
+<li class="topa"><a href="xhtml/007_chapter003.xhtml" ><strong>III</strong> <em>The Sisters</em></a></li>
+<li class="topa"><a href="xhtml/008_chapter004.xhtml"><strong>IV</strong> <em>Mother and Child</em></a></li>
+<li class="hidden_toc"><a href="xhtml/009_about_author.xhtml" >About the Author</a></li>
+<li class="hidden_toc"><a href="xhtml/010_copyright.xhtml" >Copyright</a></li>
 </ol>
 </nav>
 <nav class="hidden_toc" epub:type="landmarks">

And the pack the epub again:

zip -X b.epub mimetype && zip -rg b.epub META-INF -x \*.DS_Store && zip -rg b.epub OEBPS -x \*.DS_Store

its working.

Summary: Epub.js will look into the content of the file specified as "nav" in package.opf

<item href="xhtml/004_contents.xhtml" id="contents" media-type="application/xhtml+xml" properties="nav"/>

and its urls needs to be related to

-<li class="hidden_toc"><a href="001_cover.xhtml">Cover</a></li>
+<li class="hidden_toc"><a href="xhtml/001_cover.xhtml">Cover</a></li>