View Full Version : Reading ePUBs in a browser


Jellby
07-19-2009, 06:11 AM
Those who have seen my ePUB uploads probably know that I like to supply a set of files (index.html, index.xhtml, index.css) that allow to view the uncompressed ePUB in a web browser, with a frames layout. I create these files by hand (with extensive copy&paste) for every project, but I thought that I could also try to write a script or something that would create them automatically, and be useful for reading any ePUB.

There already exist something similar (epubjs (http://blog.threepress.org/2009/02/09/introducing-epubjs/)), but I wanted it simpler. So, this is my try. It is a bash script (so it works on Linux, maybe on OS X, and you'd need a Cygwin environment for Windows, I guess) that uncompresses an ePUB file to a temporary directory, creates the index.* files, outputs the path of the index.html (so you can paste it in your browser) and waits; when you press <Enter>, it deletes the temporary directory, and everything is as it was before.

Some features:

The left frame has a list of all files in the <spine> in order (with their "id" as names), files with "linear=no" are marked with an asterisk. At the top there are four arrow links, clicking on "<" or ">" moves to the previous or next file in the book (skipping entries with asterisks), "<<<" and ">>>" are the first and last files. You can click on any file in the list, and the "<" and ">" links work as expected (but they don't if you follow links in the main book frame). If you click on the red "TOC", the left frame view should change to the TOC instead of the <spine>, but it's not implemented yet :rolleyes:

After any change in the main book frame, the index.css file (which is defined inside the script) is applied on top of the normal CSS files of the ePUB, that means text size and layout can change, and anchors may not be as expected (this probably depends on the browser).

For the future I'd like to implement the TOC properly, and I'd also like to make it work directly from the browser, as a sort of CGI script, but I'm afraid it's not possible without installing a local web server, and that's not what I want.

Disclaimer: I tried to make the script safe and clean, but I'm no expert programmer, I basically learn along as I try new things. The script was only tried in Linux, with the Opera browser, and with the ePUB files I have created... I hope it will work in other cases, but there's no guarantee.

By the way, just run the script as:

./epub-read.sh Some_book.epub

EDIT: There was a (possibly serious) bug in the previous version, which had been downloaded once, I had "rm -r $TMPDIR" instead of "rm -r $TEMPDIR". It's corrected now.

Jellby
07-20-2009, 08:16 AM
I think the TOC is working now :) It looks fine with my ePUBs, at least. Note that no attempt is done at fixing problematic files, and that it assumes linebreaks between elements in the .opf and .ncx files.

It also worked fine with a couple arbitrary Calibre-generated ePUBs I downloaded from MR, I'm happy :)

Jellby
08-10-2009, 01:15 PM
Some additional fixes.

Jellby
09-12-2009, 05:42 AM
Another version. Now it accepts an optional flag -o, that will call kfmclient to open the HTML with the default KDE application (sorry, no Gnome alternative yet, but I'm open to suggestions).

I'll call this version 1.0 :)

SBT
12-09-2011, 06:23 AM
Here's a rather quick&dirty patch which includes a -v option, which lets you view the epub-file in lynx. Haven't had a chance to test many epub-files on it, but it seems to work with xhtml UTF-8 files.

I rip out any lines in xhtml files than contain meta.. charset= info, as otherwise lynx just displays blobs instead of any character that's not strictly ascii. A more robust method would be to use html tidy, I suppose, and convert from xhtml to html.

AZdave
12-09-2011, 03:09 PM
SBT:

Thanks. Just to be clear for others. I had to


patch epub-read.sh epub-read_lynxviewer.patch
epub-read.sh -v Oz_Omnibus.epub


I also turned images on in the lynx.conf file by adding the following.

XLOADIMAGE_COMMAND:xv %s &
MAKE_LINKS_FOR_ALL_IMAGES:TRUE


Thanks

farvardin
12-16-2011, 03:08 AM
Thank you for this useful script (and the patch). I've uploaded the patched version on my repository:

https://code.google.com/p/textallion/source/browse/contrib/others/epub-read.sh

SBT
03-12-2012, 03:28 PM
Thought I'd have a stab at making a dynamic web page capable of reading the content.xml/.opf/.ncx files, and then creating a toc and displaying the chapters. I've sort of succeeded; this page works in firefox, just save it at the same level as META-INF in a blown-up epub.
The akward issue is loading files dynamically into an iframe; security-conscious browsers are typically not fond of doing this for local files. Chrome should be able to do it if you use the --allow-file-access-from-files, but I can't get it to work. Firefox also behaves rather strangely; it reports an error for line 233, because the basis variable is undeclared, but if I declare it, it no longer reads loaded files properly.
I ran a bit amuck with css3, I'm afraid. Couldn't resist playing with all the nice new shiny bells and whistles.:o

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF8" />
<style type="text/css">
div#innh p:hover {
opacity:1;
background:yellow;
background: -moz-linear-gradient( left ,#ffff55, #995522);
}
div#spine {
position:relative;
left:2px;
top:-4px;
width:60px;
}
div#innh {
opacity:0.95;
border-style:solid double solid none;
border-color:#300808;
border-width:4px 8px 4px;
position:absolute;
z-index:1;
width:400px;
height:823px;
background:lightgreen;
background: -moz-linear-gradient( left ,#9f6a46, #490d0d);
color: -moz-linear-gradient( right ,#11aa55, #dfaa66);
-moz-transform: scaleX(0.05);
-moz-transition:-moz-transform 0.5s;
-moz-transform-origin:left center;
top:-384px;
left:80px;
overflow:hidden;
border-radius:0 20px 20px 0;
}
div#innh p {
margin:0 0 0 20px;
font-family:sans-serif;
white-space:nowrap;
overflow:hidden;
}
div#innh:hover{
-moz-transform: scaleX(1);
min-height:823px;
height:auto;
}
div#chaps p{
margin:0 0 0 0px;
font-family:sans-serif;
white-space:nowrap;
overflow:hidden;
}
div#chaps {
opacity:0;
-moz-transition:opacity 1s;
}
body {
background:#dfaa66;
min-height:600px;
background: -moz-radial-gradient(300px 400px 65deg, ellipse cover, green 50%,blue 100%);
}
div#help{
background:cyan;
background: -moz-linear-gradient( top,#088 , cyan, cyan, cyan,white);
border-color:darkblue ;
border-style:none solid ridge solid;
border-width: 8px 1px 8px 1px;
position:absolute;
height:0px;
width:595px;
top:18px;
left:118px;
overflow-y:auto;
z-index:3;
-moz-transition:height 0.6s;
-moz-transform-origin:center top;
border-radius:0 0 29px 29px ;
font-family:sans-serif;
visibility:hidden;
}
div#help hr{ margin:0 15% ;}
div#help h2 {
text-align:center;
margin:1em 0 0.5em 0 ;
text-shadow:2px 2px 2px #044;
}
div#help p{margin:1em;}

.knapp{
display:block;
text-align:center;
text-shadow:2px 2px 4px blue;
line-height:24px;
font-size:24px;
color:darkblue;
width:24px;
height:24px;
background:red;
border-style:outset;
border-width:3px;
border-color:darkblue;
background: -moz-radial-gradient(10px 12px 65deg, circle cover, #99f 5%,blue 80%);
border-radius:6px;
}
div.knapp:hover {
color:green;
}
div.knapp:active {
border-style:inset;
background: -moz-radial-gradient(10px 12px 65deg, circle cover, #448 5%,#00a 60%);
}

div.hjelp {
width:70px;
border-color:;
color:#442;
font-size:19px;
margin:20px;
text-shadow:1px 1px 3px maroon;
background: -moz-radial-gradient(10px 12px 65deg, circle cover, #ff8 5%,#aa0 80%);
}
div.hjelp:active {
background: -moz-radial-gradient(10px 12px 65deg, circle cover, #884 5%,#550 80%);
}
iframe#ramme {
background:white;
position:absolute;
top:10px;
left:108px;
height:800px;
width:615px;
z-index:0;
border-style:ridge double outset none;
border-width:11px;
border-color:brown;
border-radius:0 9px 9px 0;
}

p#tittel {
font-size:36px;
font-style:italic;
font-weight:bold;
text-shadow:2px 2px 5px maroon;
text-align:center;
padding-bottom:14px;
color:#a68120;
border-color:#300808;
margin-left:-358px;
margin-top:393px;
background:cyan;
border-style:double solid none solid;
border-width:5px 4px 2px 3px;
-moz-transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
background: -moz-linear-gradient( bottom,#9f6a46, #490d0d);
width:824px;
border-radius:20px 20px 0 0;
}

p.nav {
letter-spacing:0.36em;
text-shadow:1px 1px 3px #842;
margin-top:2px;
float:left;
width:308px;
text-align:center;
font-family:sans-serif;
font-weight:bold;
font-style:italic;
border-style:solid;
color:maroon;
background:yellow;
position:absolute;
top:828px;
}
p#next:active { background: -moz-linear-gradient( left ,#ff9f55, #300502); }
p#next:hover { color:#311; }
p#next {
background: -moz-linear-gradient( left ,#ff9f55, #793522);
border-bottom-right-radius:20px;
border-top-right-radius:20px;
left:420px;
}
p#prev:active { background: -moz-linear-gradient( right ,#ff9f55, #300502); }
p#prev:hover { color:#311; }
p#prev {
background: -moz-linear-gradient( right ,#ff9f55, #793522);
border-top-left-radius:20px;
border-bottom-left-radius:20px;
left:110px;
}
div#controls {
position:absolute;
top:30px;
left:750px;
width:110px;
height:180px;
border:ridge;
border-color:green;
border-radius:25px;
margin-left:auto;
margin-right:auto;
background: -moz-radial-gradient(55px 90px 65deg, ellipse cover, #084 80%,lightgreen 100%);

}
</style>
<script type="text/javascript">
var fs=25
var opfFile=""
var ncxFile=""
var author=""
var title=""
var titlepage=""
var indx=0
var toggle=new Boolean()
var urlArr=new Array()
var titleArr=new Array()
var spineArr=new Array()
var tocTxtArr=new Array()
var tocUrlArr=new Array()
var toggleHelp=new Boolean()

function readFile(func, file) {
document.getElementById("ramme").src=file
var t=setTimeout(func+'();', 100)
}

function readContentXml() {
opfFile=document.getElementById("ramme").contentDocument.getElementsByTagName("container")[0].getElementsByTagName("rootfiles")[0].getElementsByTagName("rootfile")[0].getAttribute("full-path")
basis=opfFile.replace(/\/[^/]*$/,"/")
readFile("readOpf", opfFile)
}

function readOpf() {
var d=document.getElementById("ramme").contentDocument.getElementsByTagName("package")[0]
var spineNodes=d.getElementsByTagName("spine")[0].getElementsByTagName("itemref")
var manifestNodes=d.getElementsByTagName("manifest")[0].getElementsByTagName("item")
var i=0
var j=0
for (j=0;j<spineNodes.length;j++) {
for (i=0;i<manifestNodes.length;i++) {
if (manifestNodes[i].getAttribute("id")==spineNodes[j].getAttribute("idref")) {
spineArr[j]=manifestNodes[i].getAttribute("href")
}
}
}
for (i=0;i<manifestNodes.length;i++) {
if (manifestNodes[i].getAttribute("id")==d.getElementsByTagName("spine")[0].getAttribute("toc")) {
ncxFile=manifestNodes[i].getAttribute("href")
}
}
titlepage=d.getElementsByTagName("guide")[0].getElementsByTagName("reference")[0].getAttribute("href")
author=d.getElementsByTagName("metadata")[0].getElementsByTagName("dc:creator")[0].childNodes[0].nodeValue
title=d.getElementsByTagName("metadata")[0].getElementsByTagName("dc:title")[0].childNodes[0].nodeValue
readFile("readNcx", basis+ncxFile)
}

function readNcx() {
var d=document.getElementById("ramme").contentDocument.getElementsByTagName("ncx")[0]
var navPoints=d.getElementsByTagName("navMap")[0].getElementsByTagName("navPoint")
for (i=0;i<navPoints.length;i++) {
tocUrlArr[i]=navPoints[i].getElementsByTagName("content")[0].getAttribute("src")
tocTxtArr[i]= navPoints[i].getElementsByTagName("text")[0].childNodes[0].nodeValue;
}
toggleToc()
document.getElementById("ramme").src=basis+titlepage
document.getElementById("ramme").style.visibility="visible"
document.getElementById("tittel").innerHTML=author+": "+title
}

function fillToc() {
var i=0
var d=document.getElementById("chaps");
d.innerHTML=""
for (i=0; i<titleArr.length;i++) {
d.innerHTML+="<p onClick=\"showChap("+i+")\" id=\""+i+"\">"+titleArr[i]+"</p>"
}
}

function toggleToc() {
var tmp=""
toggle=document.getElementById("toggler").checked
if (urlArr.length>0) tmp=urlArr[indx]
if (toggle) {
titleArr=spineArr
urlArr=spineArr
}
else {
titleArr=tocTxtArr
urlArr=tocUrlArr
}
fillToc()
if (tmp.length>0) {
var i=0
for (i=0;i<urlArr.length;i++) {
if (tmp==urlArr[i]) indx=i
}
}
document.getElementById(indx).style.fontWeight="bold"
}
function hideToc() {
document.getElementById('innh').style.MozTransform ='scaleX(0.05)'
document.getElementById('chaps').style.opacity=0
document.getElementById('innh').style.height='823p x'
}
function showToc() {
if (toggleHelp) {
document.getElementById('innh').style.MozTransform ='scaleX(1)'
document.getElementById('innh').style.height='auto '
document.getElementById('innh').style.minHeight='8 23px'
document.getElementById('chaps').style.opacity=1
}
}
function nextPrev(t) {
indx+=t
if (indx<0) {indx=0}
if (indx>=urlArr.length) {indx=(urlArr.length-1)}
showChap(indx)
}
function showHelp() {
if (toggleHelp) {
document.getElementById('help').style.visibility="visible"
document.getElementById('help').style.height='600p x'
var t=setTimeout("document.getElementById('help').style.overflowY='a uto'",1000)
}
else {
document.getElementById('help').style.overflowY='h idden'
document.getElementById('help').style.height='0px'
var t=setTimeout("document.getElementById('help').style.visibility=' hidden'",1000)
}
toggleHelp=!toggleHelp
}

function scaleFont(t) {
fs+=t
document.getElementById("ramme").contentDocument.getElementsByTagName("body")[0].style.fontSize=fs+"px"
}

function showChap(n) {
document.getElementById("ramme").src=basis+urlArr[n]
var t=setTimeout('document.getElementById("ramme").contentDocument.getElementsByTagName("body")[0].style.fontSize=fs+"px"',50)
var i=0
for (i=0;i<urlArr.length;i++) {
document.getElementById(i).style.fontWeight="normal"
}
document.getElementById(n).style.fontWeight="bold"
indx=n
}

function init() {
var msg="Only works with Firefox, I'm afraid..."
document.getElementById("ramme").src="META-INF/container.xml"
if (navigator.appVersion.indexOf("Chrome")>-1) {alert(msg+"\nYou could try starting chrome with --allow-file-access-from-local-files")}
if (navigator.appName.indexOf("Opera")>-1) {alert(msg)}
document.getElementById("ramme").style.visibility="hidden"
readFile("readContentXml", "META-INF/container.xml")
document.getElementById("ramme").src=basis+titlepage
}
</script>
</head>
<body onLoad="init()">
<p onClick="nextPrev(-1)" class="nav" id="prev">Previous Chapter</p>
<p onClick="nextPrev(1)" class="nav" id="next">Next Chapter</p>
<iframe name="ramme" id="ramme" src="META-INF/container.xml" ></iframe>

<div id="help" onClick="showHelp()">
<h2>SBT's Attempt at a Viewer for Epub</h2>
<hr>
<ul>
<li> Only works with Firefox when reading from files. If it is accessed through a web server, it should in theory work in all major browsers. Chrome shoul also work if started wit <tt>--allow-file-access-from-files</tt>, but I've not been successful.
<li> Unzip your epub-file, and save this web page on the top level of the unzipped directory structure, i.e. on the same level as META-INF (not <em>in</em> META-INF)
<li>Move the pointer over the book spine to see and select from list of contents.
<li> Use buttons to increase/decrease font size.
<li> If 'Spine' box is checked, the table of contents lists the xhtml files listed in the <tt>&lt;spine&gt;</tt> section of the OPF-manifest file.
<li>Viewing window is 600x800, so this viewer can be handy when crafting an epub book to see how it looks on a standard e-reader screen.
<li> Click inside the help window or on the help button to make this info go away.
</ul>

<p> No copyright claimed, but an acknowledgement is always appreciated if you use this web page to create a derived work.</p>
<div style="margin:2em 2em 1em 60%">
<p>
<em> SBT
9. March 2012</em></p>
</div>
</div>
<div id="spine" onMouseOut="hideToc()" onMouseOver="showToc()"><div> <p id="tittel">(Author:Title)</p> </div>

<div id="innh">
<div id="chaps">
</div>
</div>
</div>
<div id="controls">
<div class="knapp hjelp" onClick="showHelp()" id="helpbt">Help</div>
<div style="height:30px;margin-bottom:20px;">
<div class="knapp" onClick="scaleFont(-1)" style="margin-left:22px;;float:left"><span style="font-size:10px;">A</span></div>

<div class="knapp" onClick="scaleFont(1)"style="margin-right:22px;float:right"><span style="font-size:20px;">A</span> </div>
</div>
<form>
<input type="checkbox" id="toggler" onClick="toggleToc()" value="true" ><span style="font-size:21px;color:lightgreen;text-shadow:2px 2px 2px #f44;}">Spine</span></input>
</form>
</body>
</html>

AZdave
03-16-2012, 03:10 PM
@SBT:

Why not just use the firefox add-on EPUBreader?
But it is so much fun to do it yourself.

AZdave

SBT
03-16-2012, 04:19 PM
What?? Has somebody already done it? Darn...;)
My feeble claim to usefulness for this is that you can stick it in your epub file, and not require any modifications to your browser, and it can be nice to have for visually inspecting an epub during development. And it's rather fun messing about with CSS3 and javascript. I'm still fiddling about with it, and have got it working in Chrome and Opera too. Trying to get javascript unzipping and html5 file reading to work as well.