1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-02 14:49:16 +02:00
This commit is contained in:
kmacshane 2023-05-15 22:03:08 -07:00 committed by GitHub
commit 184520769d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1024 additions and 21196 deletions

325
espark_reader/reader.css Normal file
View file

@ -0,0 +1,325 @@
body {
margin: 0;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
/*position: absolute;*/
height: 100%;
width: 100%;
/*min-height: 800px;*/
}
#title {
width: 900px;
min-height: 18px;
margin: 10px auto;
text-align: center;
font-size: 16px;
color: #E2E2E2;
font-weight: 400;
}
#title:hover {
color: #777;
}
#viewer {
width: 900px;
height: 600px;
box-shadow: 0 0 4px #ccc;
padding: 10px 10px 0px 10px;
margin: 5px auto;
background: white;
}
#viewer.spreads {
width: 900px;
height: 600px;
box-shadow: 0 0 4px #ccc;
border-radius: 5px;
padding: 0;
position: relative;
margin: 10px auto;
background: white;
top: calc(50vh - 400px);
}
/* Smartphone - Portrait */
@media only screen and (min-width: 320px) and (max-width: 667px) and (orientation: portrait) {
#viewer {
width: 320px;
height: 667px;
}
}
/* Smartphone - Landscape */
@media only screen and (min-width: 320px) and (max-width: 667px) and (orientation: landscape) {
#viewer {
width: 667px;
height: 320px;
}
}
/* Tablet - Portrait and Landscape */
@media only screen and (min-width: 667px) and (max-width: 1024px) {
#viewer {
width: 600px;
height: 600px;
}
}
#viewer.spreads .epub-view>iframe {
background: white;
}
#viewer.scrolled {
overflow: hidden;
width: 800px;
margin: 0 auto;
position: relative;
background: url('ajax-loader.gif') center center no-repeat;
box-shadow: 0 0 4px #ccc;
padding: 20px;
background: white;
}
#viewer.scrolled .epub-view>iframe {
background: white;
}
#prev {
left: 0;
}
#next {
right: 0;
}
#toc {
display: block;
margin: 10px auto;
}
@media (min-width: 1000px) {
/*#viewer.spreads:after {
position: absolute;
width: 1px;
border-right: 1px #000 solid;
height: 90%;
z-index: 1;
left: 50%;
margin-left: -1px;
top: 5%;
opacity: .15;
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
content: "";
}
#viewer.spreads.single:after {
display: none;
}*/
#prev {
left: 40px;
}
#next {
right: 40px;
}
}
.arrow {
position: fixed;
top: 50%;
margin-top: -32px;
font-size: 64px;
color: #E2E2E2;
font-family: arial, sans-serif;
font-weight: bold;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
text-decoration: none;
}
.navlink {
margin: 14px;
display: block;
text-align: center;
text-decoration: none;
color: #ccc;
}
.arrow:hover,
.navlink:hover {
color: #777;
}
.arrow:active,
.navlink:hover {
color: #000;
}
#book-wrapper {
width: 480px;
height: 640px;
overflow: hidden;
border: 1px solid #ccc;
margin: 28px auto;
background: #fff;
border-radius: 0 5px 5px 0;
position: absolute;
}
#book-viewer {
width: 480px;
height: 660px;
margin: -30px auto;
-moz-box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
-webkit-box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
}
#book-viewer iframe {
padding: 40px 40px;
}
#controls {
position: absolute;
bottom: 16px;
left: 50%;
width: 400px;
margin-left: -200px;
text-align: center;
display: none;
}
#controls>input[type=range] {
width: 400px;
}
#navigation {
width: 400px;
height: 100vh;
position: absolute;
overflow: auto;
top: 0;
left: 0;
background: #777;
-webkit-transition: -webkit-transform .25s ease-out;
-moz-transition: -moz-transform .25s ease-out;
-ms-transition: -moz-transform .25s ease-out;
transition: transform .25s ease-out;
}
#navigation.fixed {
position: fixed;
}
#navigation h1 {
width: 200px;
font-size: 16px;
font-weight: normal;
color: #fff;
margin-bottom: 10px;
}
#navigation h2 {
font-size: 14px;
font-weight: normal;
color: #B0B0B0;
margin-bottom: 20px;
}
#navigation ul {
padding-left: 36px;
margin-left: 0;
margin-top: 12px;
margin-bottom: 12px;
width: 340px;
}
#navigation ul li {
list-style: decimal;
margin-bottom: 10px;
color: #cccddd;
font-size: 12px;
padding-left: 0;
margin-left: 0;
}
#navigation ul li a {
color: #ccc;
text-decoration: none;
}
#navigation ul li a:hover {
color: #fff;
text-decoration: underline;
}
#navigation ul li a.active {
color: #fff;
}
#navigation #cover {
display: block;
margin: 24px auto;
}
#navigation #closer {
position: absolute;
top: 0;
right: 0;
padding: 12px;
color: #cccddd;
width: 24px;
}
#navigation.closed {
-webkit-transform: translate(-400px, 0);
-moz-transform: translate(-400px, 0);
-ms-transform: translate(-400px, 0);
transform: translate(-400px, 0);
}
svg {
display: block;
}
.close-x {
stroke: #cccddd;
fill: transparent;
stroke-linecap: round;
stroke-width: 5;
}
.close-x:hover {
stroke: #fff;
}
#opener {
position: absolute;
top: 0;
left: 0;
padding: 10px;
stroke: #E2E2E2;
fill: #E2E2E2;
}
#opener:hover {
stroke: #777;
fill: #777;
}
#audioPlayer {
margin: 10px auto;
text-align: center;
font-size: 16px;
color: #E2E2E2;
font-weight: 400;
}

38
espark_reader/reader.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EPUB.js Input Example</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
<script src="../dist/epub.js"></script>
<script src="../howler.js/src/howler.core.js"></script>
<script src="reader.js"></script>
<link rel="stylesheet" type="text/css" href="reader.css">
</head>
<body>
<div id="title">
<input type="file" id="input">
<div id="audioplayer"><button id="playButton" onclick="playPause()">Play</button><button id="resetButton"
onclick="resetAudioToStart()">Reset</button></div>
</div>
<div id="viewer"></div>
<a id="prev" href="#prev" class="arrow"></a>
<a id="next" href="#next" class="arrow"></a>
<script>
init();
</script>
</body>
</html>

186
espark_reader/reader.js Normal file
View file

@ -0,0 +1,186 @@
var book = ePub();
var rendition;
var inputElement;
var audioClips = [];
var currentWord = 0;
var isPlaying = false;
function init() {
inputElement = document.getElementById("input");
inputElement.addEventListener('change', function (e) {
var file = e.target.files[0];
if (window.FileReader) {
var reader = new FileReader();
reader.onload = openBook;
reader.readAsArrayBuffer(file);
}
});
}
function openBook(e) {
var bookData = e.target.result;
var title = document.getElementById("title");
var next = document.getElementById("next");
var prev = document.getElementById("prev");
book.open(bookData, "binary");
/* console.log("opened book")
console.log(book) */
var rendition = book.renderTo("viewer", {
//manager: "continuous",
flow: "paginated",
width: "100%",
height: "100%",
snap: true
});
rendition.display();
var keyListener = function (e) {
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
rendition.next();
}
};
rendition.on("keyup", keyListener);
rendition.on("relocated", function (location) {
console.log(location);
initAudio();
});
next.addEventListener("click", function (e) {
rendition.next();
e.preventDefault();
}, false);
prev.addEventListener("click", function (e) {
rendition.prev();
e.preventDefault();
}, false);
document.addEventListener("keyup", keyListener, false);
}
function initAudio() {
var iframeList = document.getElementsByTagName("iframe");
audioClips = [];
for (let iframe of iframeList) {
var iframeDoc = iframe.contentDocument;
//get audio information
iframeDoc.querySelectorAll('par').forEach(par => {
var text = par.getElementsByTagName("text")[0]; //should only be one
var audio = par.getElementsByTagName("audio")[0]; //should only be one
var textId = text.getAttribute("src").split("#")[1];//text source format is page.xhtml#word
var textRef = iframeDoc.getElementById(textId);
var audioSrc = audio.getAttribute("src");
const contentType = "audio/mp3";
/*var sound = new Howl({
src: [`data:${contentType};base64,${audioSrc}`]
});*/
audioClips.push({
textId: textId,
text: textRef,
audio: audio,
clipBegin: parseFloat(audio.getAttribute("clipbegin")),
clipEnd: parseFloat(audio.getAttribute("clipend"))
//duration: 1000 * (parseFloat(audio.getAttribute("clipend")) - parseFloat(audio.getAttribute("clipbegin")))
})
});
//add next clip's start to the duration to account for pauses
for (let i = 0; i < audioClips.length - 1; i++) {
audioClips[i].duration = 1000 * (audioClips[i + 1].clipBegin - audioClips[i].clipBegin);
}
if (audioClips.length > 0) {
document.getElementById("audioplayer").style.visibility = "visible";
resetAudioToStart();
}
else {
document.getElementById("audioplayer").style.visibility = "hidden";
}
}
}
/**
* This is where the audio play/pause function should load and play everything on the page.
* Needs to loop through all available iframes because there may be 1-2 pages loaded separately on the page.
* I believe the audio tags will be in order per epub spec. Could look at play start and check if that's not the case.
* To play audio and go through highlighting the text and stopping at the right point, need to look at the clipBegin/clipEnd properties
* You could go through each audio tag and play/stop for each one but I think that'd cause some choppy behavior. Probably would be smoother to get the start/end for the page,
* and then track the time so that you can track the word for highlighting and pausing.
*
*
* Something to consider: will tracking the pages like this get messed up if the device rotates and only shows one page?
*/
function playPause() {
if (audioClips.length > 0) {
if (isPlaying) {
pauseAudio();
}
else {
playAudio();
}
}
}
/**
* The audio is the same for the whole book, need to play/pause the same audio tag
*/
function playAudio() {
isPlaying = true;
highlightWord();
audioClips[0].audio.play();
document.getElementById("playButton").textContent = "Pause";
}
function pauseAudio() {
isPlaying = false;
audioClips[0].audio.pause();
document.getElementById("playButton").textContent = "Play";
}
function resetAudioToStart() {
if (currentWord > 0)
audioClips[currentWord - 1].text.setAttribute("style", "");
currentWord = 0;
pauseAudio();
audioClips[0].audio.currentTime = audioClips[currentWord].clipBegin;
}
function resetAudioToWord() {
audioClips[0].audio.currentTime = audioClips[currentWord].clipBegin;
}
/**
* To highlight text with audio, search for the span with the id that matches the #id in the text src wrapping the audio. Each par tag has a text with source and the audio.
* Add a css highlight class to the span, then remove when moving to the next word.
*/
function highlightWord() {
//don't increment words if the audio is ended
if (currentWord >= audioClips.length) {
resetAudioToStart();
return;
}
if (!isPlaying) {
return;
}
/**
* note: using class add/remove doesn't work with iframes because the css is separate
*/
//highlight current word
if (currentWord > 0)
audioClips[currentWord - 1].text.setAttribute("style", "");
audioClips[currentWord].text.setAttribute("style", "background-color:yellow;");
//setup timer for when word is done being read
const myTimeout = setTimeout(highlightWord, audioClips[currentWord].duration);
//increment word index
currentWord++;
}

3
espark_reader/view.css Normal file
View file

@ -0,0 +1,3 @@
.highlight {
background: yellow;
}

View file

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -11,6 +12,7 @@
<link rel="stylesheet" type="text/css" href="examples.css">
</head>
<body>
<div id="title"><input type="file" id="input"></div>
<div id="viewer" class="spreads"></div>
@ -19,73 +21,74 @@
<script>
var book = ePub();
var rendition;
var book = ePub();
var rendition;
var inputElement = document.getElementById("input");
var inputElement = document.getElementById("input");
inputElement.addEventListener('change', function (e) {
var file = e.target.files[0];
if (window.FileReader) {
var reader = new FileReader();
reader.onload = openBook;
reader.readAsArrayBuffer(file);
inputElement.addEventListener('change', function (e) {
var file = e.target.files[0];
if (window.FileReader) {
var reader = new FileReader();
reader.onload = openBook;
reader.readAsArrayBuffer(file);
}
});
function openBook(e) {
var bookData = e.target.result;
var title = document.getElementById("title");
var next = document.getElementById("next");
var prev = document.getElementById("prev");
book.open(bookData, "binary");
rendition = book.renderTo("viewer", {
width: "100%",
height: "100%"
});
rendition.display();
var keyListener = function (e) {
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
}
});
function openBook(e){
var bookData = e.target.result;
var title = document.getElementById("title");
var next = document.getElementById("next");
var prev = document.getElementById("prev");
// Right Key
if ((e.keyCode || e.which) == 39) {
rendition.next();
}
book.open(bookData, "binary");
};
rendition = book.renderTo("viewer", {
width: "100%",
height: 600
});
rendition.on("keyup", keyListener);
rendition.on("relocated", function (location) {
console.log(location);
});
rendition.display();
var keyListener = function(e){
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
next.addEventListener("click", function (e) {
rendition.next();
}
e.preventDefault();
}, false);
};
rendition.on("keyup", keyListener);
rendition.on("relocated", function(location){
console.log(location);
});
next.addEventListener("click", function(e){
rendition.next();
e.preventDefault();
}, false);
prev.addEventListener("click", function(e){
rendition.prev();
e.preventDefault();
}, false);
prev.addEventListener("click", function (e) {
rendition.prev();
e.preventDefault();
}, false);
document.addEventListener("keyup", keyListener, false);
}
document.addEventListener("keyup", keyListener, false);
}
</script>
</body>
</html>

20921
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -59,6 +59,7 @@
"@xmldom/xmldom": "^0.7.5",
"core-js": "^3.18.3",
"event-emitter": "^0.3.5",
"howler": "^2.2.3",
"jszip": "^3.7.1",
"localforage": "^1.10.0",
"lodash": "^4.17.21",

View file

@ -1,4 +1,4 @@
import {qs, qsa, qsp, indexOfElementNode} from "./utils/core";
import { qs, qsa, qsp, indexOfElementNode } from "./utils/core";
/**
* Open Packaging Format Parser
@ -25,25 +25,25 @@ class Packaging {
* @param {document} packageDocument OPF XML
* @return {object} parsed package parts
*/
parse(packageDocument){
parse(packageDocument) {
var metadataNode, manifestNode, spineNode;
if(!packageDocument) {
if (!packageDocument) {
throw new Error("Package File Not Found");
}
metadataNode = qs(packageDocument, "metadata");
if(!metadataNode) {
if (!metadataNode) {
throw new Error("No Metadata Found");
}
manifestNode = qs(packageDocument, "manifest");
if(!manifestNode) {
if (!manifestNode) {
throw new Error("No Manifest Found");
}
spineNode = qs(packageDocument, "spine");
if(!spineNode) {
if (!spineNode) {
throw new Error("No Spine Found");
}
@ -53,7 +53,7 @@ class Packaging {
this.coverPath = this.findCoverPath(packageDocument);
this.spineNodeIndex = indexOfElementNode(spineNode);
//KEM: spine creation/reading
this.spine = this.parseSpine(spineNode, this.manifest);
this.uniqueIdentifier = this.findUniqueIdentifier(packageDocument);
@ -62,13 +62,13 @@ class Packaging {
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
return {
"metadata" : this.metadata,
"spine" : this.spine,
"manifest" : this.manifest,
"navPath" : this.navPath,
"ncxPath" : this.ncxPath,
"metadata": this.metadata,
"spine": this.spine,
"manifest": this.manifest,
"navPath": this.navPath,
"ncxPath": this.ncxPath,
"coverPath": this.coverPath,
"spineNodeIndex" : this.spineNodeIndex
"spineNodeIndex": this.spineNodeIndex
};
}
@ -78,7 +78,7 @@ class Packaging {
* @param {node} xml
* @return {object} metadata
*/
parseMetadata(xml){
parseMetadata(xml) {
var metadata = {};
metadata.title = this.getElementText(xml, "title");
@ -112,7 +112,7 @@ class Packaging {
* @param {node} manifestXml
* @return {object} manifest
*/
parseManifest(manifestXml){
parseManifest(manifestXml) {
var manifest = {};
//-- Turn items into an array
@ -121,19 +121,19 @@ class Packaging {
var items = Array.prototype.slice.call(selected);
//-- Create an object with the id as key
items.forEach(function(item){
items.forEach(function (item) {
var id = item.getAttribute("id"),
href = item.getAttribute("href") || "",
type = item.getAttribute("media-type") || "",
overlay = item.getAttribute("media-overlay") || "",
properties = item.getAttribute("properties") || "";
href = item.getAttribute("href") || "",
type = item.getAttribute("media-type") || "",
overlay = item.getAttribute("media-overlay") || "",
properties = item.getAttribute("properties") || "";
manifest[id] = {
"href" : href,
"href": href,
// "url" : href,
"type" : type,
"overlay" : overlay,
"properties" : properties.length ? properties.split(" ") : []
"type": type,
"overlay": overlay,
"properties": properties.length ? properties.split(" ") : []
};
});
@ -149,7 +149,7 @@ class Packaging {
* @param {Packaging.manifest} manifest
* @return {object} spine
*/
parseSpine(spineXml, manifest){
parseSpine(spineXml, manifest) {
var spine = [];
var selected = qsa(spineXml, "itemref");
@ -158,7 +158,7 @@ class Packaging {
// var epubcfi = new EpubCFI();
//-- Add to array to maintain ordering and cross reference with manifest
items.forEach(function(item, index){
items.forEach(function (item, index) {
var idref = item.getAttribute("idref");
// var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
var props = item.getAttribute("properties") || "";
@ -167,13 +167,13 @@ class Packaging {
// var manifestPropArray = manifestProps.length ? manifestProps.split(" ") : [];
var itemref = {
"id" : item.getAttribute("id"),
"idref" : idref,
"linear" : item.getAttribute("linear") || "yes",
"properties" : propArray,
"id": item.getAttribute("id"),
"idref": idref,
"linear": item.getAttribute("linear") || "yes",
"properties": propArray,
// "href" : manifest[Id].href,
// "url" : manifest[Id].url,
"index" : index
"index": index
// "cfiBase" : cfiBase
};
spine.push(itemref);
@ -188,13 +188,13 @@ class Packaging {
* @param {node} packageXml
* @return {string} Unique Identifier text
*/
findUniqueIdentifier(packageXml){
findUniqueIdentifier(packageXml) {
var uniqueIdentifierId = packageXml.documentElement.getAttribute("unique-identifier");
if (! uniqueIdentifierId) {
if (!uniqueIdentifierId) {
return "";
}
var identifier = packageXml.getElementById(uniqueIdentifierId);
if (! identifier) {
if (!identifier) {
return "";
}
@ -211,11 +211,11 @@ class Packaging {
* @param {element} manifestNode
* @return {string}
*/
findNavPath(manifestNode){
findNavPath(manifestNode) {
// Find item with property "nav"
// Should catch nav regardless of order
// var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
var node = qsp(manifestNode, "item", {"properties":"nav"});
var node = qsp(manifestNode, "item", { "properties": "nav" });
return node ? node.getAttribute("href") : false;
}
@ -227,9 +227,9 @@ class Packaging {
* @param {element} spineNode
* @return {string}
*/
findNcxPath(manifestNode, spineNode){
findNcxPath(manifestNode, spineNode) {
// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
var node = qsp(manifestNode, "item", {"media-type":"application/x-dtbncx+xml"});
var node = qsp(manifestNode, "item", { "media-type": "application/x-dtbncx+xml" });
var tocId;
// If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
@ -237,7 +237,7 @@ class Packaging {
// "The item that describes the NCX must be referenced by the spine toc attribute."
if (!node) {
tocId = spineNode.getAttribute("toc");
if(tocId) {
if (tocId) {
// node = manifestNode.querySelector("item[id='" + tocId + "']");
node = manifestNode.querySelector(`#${tocId}`);
}
@ -254,17 +254,17 @@ class Packaging {
* @param {node} packageXml
* @return {string} href
*/
findCoverPath(packageXml){
findCoverPath(packageXml) {
var pkg = qs(packageXml, "package");
var epubVersion = pkg.getAttribute("version");
// Try parsing cover with epub 3.
// var node = packageXml.querySelector("item[properties='cover-image']");
var node = qsp(packageXml, "item", {"properties":"cover-image"});
var node = qsp(packageXml, "item", { "properties": "cover-image" });
if (node) return node.getAttribute("href");
// Fallback to epub 2.
var metaCover = qsp(packageXml, "meta", {"name":"cover"});
var metaCover = qsp(packageXml, "meta", { "name": "cover" });
if (metaCover) {
var coverId = metaCover.getAttribute("content");
@ -284,15 +284,15 @@ class Packaging {
* @param {string} tag
* @return {string} text
*/
getElementText(xml, tag){
getElementText(xml, tag) {
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag);
var el;
if(!found || found.length === 0) return "";
if (!found || found.length === 0) return "";
el = found[0];
if(el.childNodes.length){
if (el.childNodes.length) {
return el.childNodes[0].nodeValue;
}
@ -307,10 +307,10 @@ class Packaging {
* @param {string} property
* @return {string} text
*/
getPropertyText(xml, property){
var el = qsp(xml, "meta", {"property":property});
getPropertyText(xml, property) {
var el = qsp(xml, "meta", { "property": property });
if(el && el.childNodes.length){
if (el && el.childNodes.length) {
return el.childNodes[0].nodeValue;
}
@ -326,7 +326,7 @@ class Packaging {
this.metadata = json.metadata;
let spine = json.readingOrder || json.spine;
this.spine = spine.map((item, index) =>{
this.spine = spine.map((item, index) => {
item.index = index;
item.linear = item.linear || "yes";
return item;
@ -342,20 +342,20 @@ class Packaging {
this.spineNodeIndex = 0;
this.toc = json.toc.map((item, index) =>{
this.toc = json.toc.map((item, index) => {
item.label = item.title;
return item;
});
return {
"metadata" : this.metadata,
"spine" : this.spine,
"manifest" : this.manifest,
"navPath" : this.navPath,
"ncxPath" : this.ncxPath,
"metadata": this.metadata,
"spine": this.spine,
"manifest": this.manifest,
"navPath": this.navPath,
"ncxPath": this.ncxPath,
"coverPath": this.coverPath,
"spineNodeIndex" : this.spineNodeIndex,
"toc" : this.toc
"spineNodeIndex": this.spineNodeIndex,
"toc": this.toc
};
}

View file

@ -64,7 +64,7 @@ class Rendition {
extend(this.settings, options);
if (typeof(this.settings.manager) === "object") {
if (typeof (this.settings.manager) === "object") {
this.manager = this.settings.manager;
}
@ -176,6 +176,7 @@ class Rendition {
// If manager is a string, try to load from imported managers
if (typeof manager === "string" && manager === "default") {
viewManager = DefaultViewManager;
} else if (typeof manager === "string" && manager === "continuous") {
viewManager = ContinuousViewManager;
} else {
@ -209,11 +210,11 @@ class Rendition {
* Start the rendering
* @return {Promise} rendering has started
*/
start(){
start() {
if (!this.settings.layout && (this.book.package.metadata.layout === "pre-paginated" || this.book.displayOptions.fixedLayout === "true")) {
this.settings.layout = "pre-paginated";
}
switch(this.book.package.metadata.spread) {
switch (this.book.package.metadata.spread) {
case 'none':
this.settings.spread = 'none';
break;
@ -222,7 +223,7 @@ class Rendition {
break;
}
if(!this.manager) {
if (!this.manager) {
this.ViewManager = this.requireManager(this.settings.manager);
this.View = this.requireView(this.settings.view);
@ -273,14 +274,14 @@ class Rendition {
* @param {element} element to attach to
* @return {Promise}
*/
attachTo(element){
attachTo(element) {
return this.q.enqueue(function () {
// Start rendering
this.manager.render(element, {
"width" : this.settings.width,
"height" : this.settings.height
"width": this.settings.width,
"height": this.settings.height
});
/**
@ -302,7 +303,7 @@ class Rendition {
* @param {string} target Url or EpubCFI
* @return {Promise}
*/
display(target){
display(target) {
if (this.displaying) {
this.displaying.resolve();
}
@ -315,7 +316,7 @@ class Rendition {
* @param {string} target Url or EpubCFI
* @return {Promise}
*/
_display(target){
_display(target) {
if (!this.book) {
return;
}
@ -331,10 +332,10 @@ class Rendition {
if (this.book.locations.length() && isFloat(target)) {
target = this.book.locations.cfiFromPercentage(parseFloat(target));
}
//KEM: Section display
section = this.book.spine.get(target);
if(!section){
if (!section) {
displaying.reject(new Error("No Section Found"));
return displayed;
}
@ -415,7 +416,7 @@ class Rendition {
* @private
* @param {*} view
*/
afterDisplayed(view){
afterDisplayed(view) {
view.on(EVENTS.VIEWS.MARK_CLICKED, (cfiRange, data) => this.triggerMarkEvent(cfiRange, data, view.contents));
@ -444,7 +445,7 @@ class Rendition {
* @private
* @param {*} view
*/
afterRemoved(view){
afterRemoved(view) {
this.hooks.unloaded.trigger(view, this).then(() => {
/**
* Emit that a section has been removed
@ -461,7 +462,7 @@ class Rendition {
* Report resize events and display the last seen location
* @private
*/
onResized(size, epubcfi){
onResized(size, epubcfi) {
/**
* Emit that the rendition has been resized
@ -486,7 +487,7 @@ class Rendition {
* Report orientation events and display the last seen location
* @private
*/
onOrientationChange(orientation){
onOrientationChange(orientation) {
/**
* Emit that the rendition has been rotated
* @event orientationchange
@ -501,7 +502,7 @@ class Rendition {
* Usually you would be better off calling display()
* @param {object} offset
*/
moveTo(offset){
moveTo(offset) {
this.manager.moveTo(offset);
}
@ -511,7 +512,7 @@ class Rendition {
* @param {number} [height]
* @param {string} [epubcfi] (optional)
*/
resize(width, height, epubcfi){
resize(width, height, epubcfi) {
if (width) {
this.settings.width = width;
}
@ -524,7 +525,7 @@ class Rendition {
/**
* Clear all rendered views
*/
clear(){
clear() {
this.manager.clear();
}
@ -532,7 +533,7 @@ class Rendition {
* Go to the next "page" in the rendition
* @return {Promise}
*/
next(){
next() {
return this.q.enqueue(this.manager.next.bind(this.manager))
.then(this.reportLocation.bind(this));
}
@ -541,7 +542,7 @@ class Rendition {
* Go to the previous "page" in the rendition
* @return {Promise}
*/
prev(){
prev() {
return this.q.enqueue(this.manager.prev.bind(this.manager))
.then(this.reportLocation.bind(this));
}
@ -553,7 +554,7 @@ class Rendition {
* @param {object} metadata
* @return {object} properties
*/
determineLayoutProperties(metadata){
determineLayoutProperties(metadata) {
var properties;
var layout = this.settings.layout || metadata.layout || "reflowable";
var spread = this.settings.spread || metadata.spread || "auto";
@ -564,17 +565,17 @@ class Rendition {
var direction = this.settings.direction || metadata.direction || "ltr";
if ((this.settings.width === 0 || this.settings.width > 0) &&
(this.settings.height === 0 || this.settings.height > 0)) {
(this.settings.height === 0 || this.settings.height > 0)) {
// viewport = "width="+this.settings.width+", height="+this.settings.height+"";
}
properties = {
layout : layout,
spread : spread,
orientation : orientation,
flow : flow,
viewport : viewport,
minSpreadWidth : minSpreadWidth,
layout: layout,
spread: spread,
orientation: orientation,
flow: flow,
viewport: viewport,
minSpreadWidth: minSpreadWidth,
direction: direction
};
@ -586,11 +587,11 @@ class Rendition {
* (scrolled-continuous vs scrolled-doc are handled by different view managers)
* @param {string} flow
*/
flow(flow){
flow(flow) {
var _flow = flow;
if (flow === "scrolled" ||
flow === "scrolled-doc" ||
flow === "scrolled-continuous") {
flow === "scrolled-doc" ||
flow === "scrolled-continuous") {
_flow = "scrolled";
}
@ -622,7 +623,7 @@ class Rendition {
* Adjust the layout of the rendition to reflowable or pre-paginated
* @param {object} settings
*/
layout(settings){
layout(settings) {
if (settings) {
this._layout = new Layout(settings);
this._layout.spread(settings.spread, this.settings.minSpreadWidth);
@ -646,7 +647,7 @@ class Rendition {
* @param {string} spread none | auto (TODO: implement landscape, portrait, both)
* @param {int} [min] min width to use spreads at
*/
spread(spread, min){
spread(spread, min) {
this.settings.spread = spread;
@ -667,7 +668,7 @@ class Rendition {
* Adjust the direction of the rendition
* @param {string} dir
*/
direction(dir){
direction(dir) {
this.settings.direction = dir || "ltr";
@ -686,12 +687,12 @@ class Rendition {
* @fires relocated
* @fires locationChanged
*/
reportLocation(){
return this.q.enqueue(function reportedLocation(){
reportLocation() {
return this.q.enqueue(function reportedLocation() {
requestAnimationFrame(function reportedLocationAfterRAF() {
var location = this.manager.currentLocation();
if (location && location.then && typeof location.then === "function") {
location.then(function(result) {
location.then(function (result) {
let located = this.located(result);
if (!located || !located.start || !located.end) {
@ -753,10 +754,10 @@ class Rendition {
* Get the Current Location object
* @return {displayedLocation | promise} location (may be a promise)
*/
currentLocation(){
currentLocation() {
var location = this.manager.currentLocation();
if (location && location.then && typeof location.then === "function") {
location.then(function(result) {
location.then(function (result) {
let located = this.located(result);
return located;
}.bind(this));
@ -772,12 +773,12 @@ class Rendition {
* @returns {displayedLocation}
* @private
*/
located(location){
located(location) {
if (!location.length) {
return {};
}
let start = location[0];
let end = location[location.length-1];
let end = location[location.length - 1];
let located = {
start: {
@ -794,7 +795,7 @@ class Rendition {
href: end.href,
cfi: end.mapping.end,
displayed: {
page: end.pages[end.pages.length-1] || 1,
page: end.pages[end.pages.length - 1] || 1,
total: end.totalPages
}
}
@ -823,12 +824,12 @@ class Rendition {
}
if (end.index === this.book.spine.last().index &&
located.end.displayed.page >= located.end.displayed.total) {
located.end.displayed.page >= located.end.displayed.total) {
located.atEnd = true;
}
if (start.index === this.book.spine.first().index &&
located.start.displayed.page === 1) {
located.start.displayed.page === 1) {
located.atStart = true;
}
@ -838,7 +839,7 @@ class Rendition {
/**
* Remove and Clean Up the Rendition
*/
destroy(){
destroy() {
// Clear the queue
// this.q.clear();
// this.q = undefined;
@ -873,7 +874,7 @@ class Rendition {
* @private
* @param {Contents} view contents
*/
passEvents(contents){
passEvents(contents) {
DOM_EVENTS.forEach((e) => {
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
});
@ -886,7 +887,7 @@ class Rendition {
* @private
* @param {event} e
*/
triggerViewEvent(e, contents){
triggerViewEvent(e, contents) {
this.emit(e.type, e, contents);
}
@ -895,7 +896,7 @@ class Rendition {
* @private
* @param {string} cfirange
*/
triggerSelectedEvent(cfirange, contents){
triggerSelectedEvent(cfirange, contents) {
/**
* Emit that a text selection has occurred
* @event selected
@ -911,7 +912,7 @@ class Rendition {
* @private
* @param {EpubCFI} cfirange
*/
triggerMarkEvent(cfiRange, data, contents){
triggerMarkEvent(cfiRange, data, contents) {
/**
* Emit that a mark was clicked
* @event markClicked
@ -929,10 +930,10 @@ class Rendition {
* @param {string} ignoreClass
* @return {range}
*/
getRange(cfi, ignoreClass){
getRange(cfi, ignoreClass) {
var _cfi = new EpubCFI(cfi);
var found = this.manager.visible().filter(function (view) {
if(_cfi.spinePos === view.index) return true;
if (_cfi.spinePos === view.index) return true;
});
// Should only every return 1 item
@ -949,7 +950,7 @@ class Rendition {
adjustImages(contents) {
if (this._layout.name === "pre-paginated") {
return new Promise(function(resolve){
return new Promise(function (resolve) {
resolve();
});
}
@ -959,7 +960,7 @@ class Rendition {
let horizontalPadding = parseFloat(computed.paddingLeft) + parseFloat(computed.paddingRight);
contents.addStylesheetRules({
"img" : {
"img": {
"max-width": (this._layout.columnWidth ? (this._layout.columnWidth - horizontalPadding) + "px" : "100%") + "!important",
"max-height": height + "px" + "!important",
"object-fit": "contain",
@ -967,7 +968,7 @@ class Rendition {
"break-inside": "avoid",
"box-sizing": "border-box"
},
"svg" : {
"svg": {
"max-width": (this._layout.columnWidth ? (this._layout.columnWidth - horizontalPadding) + "px" : "100%") + "!important",
"max-height": height + "px" + "!important",
"page-break-inside": "avoid",
@ -975,9 +976,9 @@ class Rendition {
}
});
return new Promise(function(resolve, reject){
return new Promise(function (resolve, reject) {
// Wait to apply
setTimeout(function() {
setTimeout(function () {
resolve();
}, 1);
});
@ -987,7 +988,7 @@ class Rendition {
* Get the Contents object of each rendered view
* @returns {Contents[]}
*/
getContents () {
getContents() {
return this.manager ? this.manager.getContents() : [];
}
@ -995,7 +996,7 @@ class Rendition {
* Get the views member from the manager
* @returns {Views}
*/
views () {
views() {
let views = this.manager ? this.manager.views : undefined;
return views || [];
}

View file

@ -1,5 +1,5 @@
import {substitute} from "./utils/replacements";
import {createBase64Url, createBlobUrl, blob2base64} from "./utils/core";
import { substitute } from "./utils/replacements";
import { createBase64Url, createBlobUrl, blob2base64 } from "./utils/core";
import Url from "./utils/url";
import mime from "./utils/mime";
import Path from "./utils/path";
@ -30,16 +30,17 @@ class Resources {
* Process resources
* @param {Manifest} manifest
*/
process(manifest){
process(manifest) {
this.manifest = manifest;
this.resources = Object.keys(manifest).
map(function (key){
map(function (key) {
return manifest[key];
});
this.replacementUrls = [];
this.html = [];
//KEM: assets and urls are where the urls in items from the manifest are replaced by "blobs"
this.assets = [];
this.css = [];
@ -54,29 +55,29 @@ class Resources {
* Split resources by type
* @private
*/
split(){
split() {
// HTML
this.html = this.resources.
filter(function (item){
filter(function (item) {
if (item.type === "application/xhtml+xml" ||
item.type === "text/html") {
item.type === "text/html") {
return true;
}
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
filter(function (item) {
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
filter(function (item) {
if (item.type === "text/css") {
return true;
}
@ -87,16 +88,16 @@ class Resources {
* Convert split resources into Urls
* @private
*/
splitUrls(){
splitUrls() {
// All Assets Urls
this.urls = this.assets.
map(function(item) {
map(function (item) {
return item.href;
}.bind(this));
// Css Urls
this.cssUrls = this.css.map(function(item) {
this.cssUrls = this.css.map(function (item) {
return item.href;
});
@ -107,12 +108,12 @@ class Resources {
* @param {string} url
* @return {Promise<string>} Promise resolves with url string
*/
createUrl (url) {
createUrl(url) {
var parsedUrl = new Url(url);
var mimeType = mime.lookup(parsedUrl.filename);
//KEM: based on the printout of the parsedUrl here, the url is correct, but the smil file is referring to it differently. May need to modify the replacement to include the ../
if (this.settings.archive) {
return this.settings.archive.createUrl(url, {"base64": (this.settings.replacements === "base64")});
return this.settings.archive.createUrl(url, { "base64": (this.settings.replacements === "base64") });
} else {
if (this.settings.replacements === "base64") {
return this.settings.request(url, 'blob')
@ -134,27 +135,27 @@ class Resources {
* Create blob urls for all the assets
* @return {Promise} returns replacement urls
*/
replacements(){
replacements() {
if (this.settings.replacements === "none") {
return new Promise(function(resolve) {
return new Promise(function (resolve) {
resolve(this.urls);
}.bind(this));
}
var replacements = this.urls.map( (url) => {
var absolute = this.settings.resolver(url);
var replacements = this.urls.map((url) => {
var absolute = this.settings.resolver(url);
return this.createUrl(absolute).
catch((err) => {
console.error(err);
return null;
});
});
return this.createUrl(absolute).
catch((err) => {
console.error(err);
return null;
});
});
return Promise.all(replacements)
.then( (replacementUrls) => {
.then((replacementUrls) => {
this.replacementUrls = replacementUrls.filter((url) => {
return (typeof(url) === "string");
return (typeof (url) === "string");
});
return replacementUrls;
});
@ -167,11 +168,11 @@ class Resources {
* @param {method} [resolver]
* @return {Promise}
*/
replaceCss(archive, resolver){
replaceCss(archive, resolver) {
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
this.cssUrls.forEach(function (href) {
var replacement = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
@ -193,11 +194,11 @@ class Resources {
* @param {string} href the original css file
* @return {Promise} returns a BlobUrl to the new CSS file or a data url
*/
createCssFile(href){
createCssFile(href) {
var newUrl;
if (path.isAbsolute(href)) {
return new Promise(function(resolve){
return new Promise(function (resolve) {
resolve();
});
}
@ -214,7 +215,7 @@ class Resources {
}
// Get asset links relative to css file
var relUrls = this.urls.map( (assetHref) => {
var relUrls = this.urls.map((assetHref) => {
var resolved = this.settings.resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
@ -223,12 +224,12 @@ class Resources {
if (!textResponse) {
// file not found, don't replace
return new Promise(function(resolve){
return new Promise(function (resolve) {
resolve();
});
}
return textResponse.then( (text) => {
return textResponse.then((text) => {
// Replacements in the css text
text = substitute(text, relUrls, this.replacementUrls);
@ -242,7 +243,7 @@ class Resources {
return newUrl;
}, (err) => {
// handle response errors
return new Promise(function(resolve){
return new Promise(function (resolve) {
resolve();
});
});
@ -255,14 +256,18 @@ class Resources {
* @param {resolver} [resolver]
* @return {string[]} array with relative Urls
*/
relativeTo(absolute, resolver){
relativeTo(absolute, resolver) {
resolver = resolver || this.settings.resolver;
// Get Urls relative to current sections
return this.urls.
map(function(href) {
map(function (href) {
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
//KEM: hardcoding to fix audio links for testing
//KEM: these are the links that are searched for in the content
if (relative.includes("audio")) {
relative = "../" + relative;
}
return relative;
}.bind(this));
}
@ -278,7 +283,7 @@ class Resources {
return;
}
if (this.replacementUrls.length) {
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
resolve(this.replacementUrls[indexInUrls]);
}.bind(this));
} else {
@ -300,6 +305,12 @@ class Resources {
} else {
relUrls = this.urls;
}
//KEM: this seems to be where the audio urls are going wrong, but it could be that there's a problem with the original file.
//KEM: The smil files have relative paths to the resources with an extra folder jump like ../, and the regular pages don't do that
//KEM: so there may need to be some kind of check to see where the files actually are before replacing them?
/**
* Goes through the content and replaces any instances of relUrls with this.replacementUrls
*/
return substitute(content, relUrls, this.replacementUrls);
}

View file

@ -14,7 +14,7 @@ import { DOMParser as XMLDOMSerializer } from "@xmldom/xmldom";
* @param {object} hooks hooks for serialize and content
*/
class Section {
constructor(item, hooks){
constructor(item, hooks) {
this.idref = item.idref;
this.linear = item.linear === "yes";
this.properties = item.properties;
@ -24,7 +24,7 @@ class Section {
this.canonical = item.canonical;
this.next = item.next;
this.prev = item.prev;
this.overlay = item.overlay;
this.cfiBase = item.cfiBase;
if (hooks) {
@ -38,6 +38,7 @@ class Section {
this.document = undefined;
this.contents = undefined;
this.output = undefined;
this.mediaOverlay = undefined;
}
/**
@ -45,29 +46,60 @@ class Section {
* @param {method} [_request] a request method to use for loading
* @return {document} a promise with the xml document
*/
load(_request){
load(_request) {
var request = _request || this.request || Request;
var loading = new defer();
var loaded = loading.promise;
if(this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(function(xml){
// var directory = new Url(this.url).directory;
//KEM: this is where the file is loaded and turned into xml
//KEM: add in a load to the smil file and append it?
//KEM: try to load overlay
if (this.overlay) {
if (this.contents) {
loading.resolve(this.contents);
} else {
request(this.overlay.url).then(function (overlayXml) {
var div = document.createElement("div");
div.classList.add("audioContainer");
//overlay is returning as a string? possibly because xml instead of xhtml
var start = overlayXml.search("<smil");
var xmlStr = overlayXml.substring(start);
div.insertAdjacentHTML('beforeend', xmlStr);
this.mediaOverlay = div;
return request(this.url).then(function (xml) {
this.document = xml;
this.contents = xml.documentElement;
this.contents.appendChild(div);
return this.hooks.content.trigger(this.document, this);
}.bind(this)).then(function () {
loading.resolve(this.contents);
}.bind(this)).catch(function (error) {
loading.reject(error);
});
}
.bind(this)).catch(function (error) {
loading.reject(error);
});
}
}
else {
if (this.contents) {
loading.resolve(this.contents);
} else {
request(this.url).then(function (xml) {
// var directory = new Url(this.url).directory;
this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
}.bind(this)).then(function () {
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
}.bind(this)).catch(function (error) {
loading.reject(error);
});
}
}
return loaded;
@ -77,7 +109,7 @@ class Section {
* Adds a base tag for resolving urls in the section
* @private
*/
base(){
base() {
return replaceBase(this.document, this);
}
@ -86,13 +118,13 @@ class Section {
* @param {method} [_request] a request method to use for loading
* @return {string} output a serialized XML Document
*/
render(_request){
render(_request) {
var rendering = new defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
//console.log("rendering section");
this.load(_request).
then(function(contents){
then(function (contents) {
var userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
var isIE = userAgent.indexOf('Trident') >= 0;
var Serializer;
@ -105,13 +137,13 @@ class Section {
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
then(function () {
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
then(function () {
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
.catch(function (error) {
rendering.reject(error);
});
@ -123,11 +155,11 @@ class Section {
* @param {string} _query The query string to find
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
find(_query){
find(_query) {
var section = this;
var matches = [];
var query = _query.toLowerCase();
var find = function(node){
var find = function (node) {
var text = node.textContent.toLowerCase();
var range = section.document.createRange();
var cfi;
@ -153,7 +185,7 @@ class Section {
excerpt = node.textContent;
}
else {
excerpt = node.textContent.substring(pos - limit/2, pos + limit/2);
excerpt = node.textContent.substring(pos - limit / 2, pos + limit / 2);
excerpt = "..." + excerpt + "...";
}
@ -168,7 +200,7 @@ class Section {
}
};
sprint(section.document, function(node) {
sprint(section.document, function (node) {
find(node);
});
@ -182,43 +214,43 @@ class Section {
* @param {int} maxSeqEle The maximum number of Element that are combined for search, default value is 5.
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
search(_query , maxSeqEle = 5){
if (typeof(document.createTreeWalker) == "undefined") {
search(_query, maxSeqEle = 5) {
if (typeof (document.createTreeWalker) == "undefined") {
return this.find(_query);
}
let matches = [];
const excerptLimit = 150;
const section = this;
const query = _query.toLowerCase();
const search = function(nodeList){
const textWithCase = nodeList.reduce((acc ,current)=>{
const search = function (nodeList) {
const textWithCase = nodeList.reduce((acc, current) => {
return acc + current.textContent;
},"");
}, "");
const text = textWithCase.toLowerCase();
const pos = text.indexOf(query);
if (pos != -1){
const startNodeIndex = 0 , endPos = pos + query.length;
let endNodeIndex = 0 , l = 0;
if (pos < nodeList[startNodeIndex].length){
if (pos != -1) {
const startNodeIndex = 0, endPos = pos + query.length;
let endNodeIndex = 0, l = 0;
if (pos < nodeList[startNodeIndex].length) {
let cfi;
while( endNodeIndex < nodeList.length - 1 ){
while (endNodeIndex < nodeList.length - 1) {
l += nodeList[endNodeIndex].length;
if ( endPos <= l){
if (endPos <= l) {
break;
}
endNodeIndex += 1;
}
let startNode = nodeList[startNodeIndex] , endNode = nodeList[endNodeIndex];
let startNode = nodeList[startNodeIndex], endNode = nodeList[endNodeIndex];
let range = section.document.createRange();
range.setStart(startNode,pos);
let beforeEndLengthCount = nodeList.slice(0, endNodeIndex).reduce((acc,current)=>{return acc+current.textContent.length;},0) ;
range.setEnd(endNode, beforeEndLengthCount > endPos ? endPos : endPos - beforeEndLengthCount );
range.setStart(startNode, pos);
let beforeEndLengthCount = nodeList.slice(0, endNodeIndex).reduce((acc, current) => { return acc + current.textContent.length; }, 0);
range.setEnd(endNode, beforeEndLengthCount > endPos ? endPos : endPos - beforeEndLengthCount);
cfi = section.cfiFromRange(range);
let excerpt = nodeList.slice(0, endNodeIndex+1).reduce((acc,current)=>{return acc+current.textContent ;},"");
if (excerpt.length > excerptLimit){
excerpt = excerpt.substring(pos - excerptLimit/2, pos + excerptLimit/2);
let excerpt = nodeList.slice(0, endNodeIndex + 1).reduce((acc, current) => { return acc + current.textContent; }, "");
if (excerpt.length > excerptLimit) {
excerpt = excerpt.substring(pos - excerptLimit / 2, pos + excerptLimit / 2);
excerpt = "..." + excerpt + "...";
}
matches.push({
@ -230,15 +262,15 @@ class Section {
}
const treeWalker = document.createTreeWalker(section.document, NodeFilter.SHOW_TEXT, null, false);
let node , nodeList = [];
let node, nodeList = [];
while (node = treeWalker.nextNode()) {
nodeList.push(node);
if (nodeList.length == maxSeqEle){
search(nodeList.slice(0 , maxSeqEle));
if (nodeList.length == maxSeqEle) {
search(nodeList.slice(0, maxSeqEle));
nodeList = nodeList.slice(1, maxSeqEle);
}
}
if (nodeList.length > 0){
if (nodeList.length > 0) {
search(nodeList);
}
return matches;
@ -250,23 +282,23 @@ class Section {
* @param {object} globalLayout The global layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
reconcileLayoutSettings(globalLayout){
reconcileLayoutSettings(globalLayout) {
//-- Get the global defaults
var settings = {
layout : globalLayout.layout,
spread : globalLayout.spread,
orientation : globalLayout.orientation
layout: globalLayout.layout,
spread: globalLayout.spread,
orientation: globalLayout.orientation
};
//-- Get the chapter's display type
this.properties.forEach(function(prop){
this.properties.forEach(function (prop) {
var rendition = prop.replace("rendition:", "");
var split = rendition.indexOf("-");
var property, value;
if(split != -1){
if (split != -1) {
property = rendition.slice(0, split);
value = rendition.slice(split+1);
value = rendition.slice(split + 1);
settings[property] = value;
}
@ -299,6 +331,7 @@ class Section {
this.document = undefined;
this.contents = undefined;
this.output = undefined;
this.mediaOverlay = undefined;
}
destroy() {

View file

@ -1,7 +1,7 @@
import EpubCFI from "./epubcfi";
import Hook from "./utils/hook";
import Section from "./section";
import {replaceBase, replaceCanonical, replaceMeta} from "./utils/replacements";
import { replaceBase, replaceCanonical, replaceMeta } from "./utils/replacements";
/**
* A collection of Spine Items
@ -45,8 +45,8 @@ class Spine {
this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || "";
this.length = this.items.length;
this.items.forEach( (item, index) => {
//KEM: unpacking opf file
this.items.forEach((item, index) => {
var manifestItem = this.manifest[item.idref];
var spineItem;
@ -58,21 +58,27 @@ class Spine {
item.canonical = canonical(item.href);
}
if(manifestItem) {
if (manifestItem) {
//KEM: This is where spine items get the URL from the manifest, this is where to connect the manifest media-overlay and get SMIL URL
item.href = manifestItem.href;
item.url = resolver(item.href, true);
item.canonical = canonical(item.href);
if(manifestItem.properties.length){
//KEM: added overlay so can load later in section
item.overlay = this.manifest[manifestItem.overlay];
if (item.overlay) {
item.overlay.url = resolver(item.overlay.href, true);
item.overlay.canonical = canonical(item.overlay.href);
}
if (manifestItem.properties.length) {
item.properties.push.apply(item.properties, manifestItem.properties);
}
}
if (item.linear === "yes") {
item.prev = function() {
item.prev = function () {
let prevIndex = item.index;
while (prevIndex > 0) {
let prev = this.get(prevIndex-1);
let prev = this.get(prevIndex - 1);
if (prev && prev.linear) {
return prev;
}
@ -80,10 +86,10 @@ class Spine {
}
return;
}.bind(this);
item.next = function() {
item.next = function () {
let nextIndex = item.index;
while (nextIndex < this.spineItems.length-1) {
let next = this.get(nextIndex+1);
while (nextIndex < this.spineItems.length - 1) {
let next = this.get(nextIndex + 1);
if (next && next.linear) {
return next;
}
@ -92,15 +98,15 @@ class Spine {
return;
}.bind(this);
} else {
item.prev = function() {
item.prev = function () {
return;
}
item.next = function() {
item.next = function () {
return;
}
}
//KEM: section creation
spineItem = new Section(item, this.hooks);
this.append(spineItem);
@ -131,14 +137,14 @@ class Spine {
}
index += 1;
}
} else if(this.epubcfi.isCfiString(target)) {
} else if (this.epubcfi.isCfiString(target)) {
let cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if(typeof target === "number" || isNaN(target) === false){
} else if (typeof target === "number" || isNaN(target) === false) {
index = target;
} else if(typeof target === "string" && target.indexOf("#") === 0) {
} else if (typeof target === "string" && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(typeof target === "string") {
} else if (typeof target === "string") {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target] || this.spineByHref[encodeURI(target)];
@ -180,7 +186,7 @@ class Spine {
this.spineById[section.idref] = 0;
// Re-index
this.spineItems.forEach(function(item, index){
this.spineItems.forEach(function (item, index) {
item.index = index;
});
@ -199,7 +205,7 @@ class Spine {
remove(section) {
var index = this.spineItems.indexOf(section);
if(index > -1) {
if (index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
@ -229,7 +235,7 @@ class Spine {
return next;
}
index += 1;
} while (index < this.spineItems.length) ;
} while (index < this.spineItems.length);
}
/**
@ -237,7 +243,7 @@ class Spine {
* @return {Section} last section
*/
last() {
let index = this.spineItems.length-1;
let index = this.spineItems.length - 1;
do {
let prev = this.get(index);

85
src/test.html Normal file
View file

@ -0,0 +1,85 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="en-US">
<head>
<title>Me and My Cat</title>
<link href="CSS/default.css" rel="stylesheet" type="text/css" />
<link href="CSS/css_1.css" rel="stylesheet" type="text/css" />
<meta name="viewport" content="width=1400, height=1685" />
</head>
<body epub:type="frontmatter" lang="en-US" xml:lang="en-US">
<div class="pg1">
<section epub:type="titlepage">
<img src="images/1.jpg" class="img" alt="written " />
<p id="para1">
<span id="p1s1"><span id="written">written</span></span>
<span id="Me"></span>
<span id="and"></span>
<span id="My"></span>
<span id="Cat"></span>
<span id="p1s2"><span id="by">by</span></span> <br />
<span id="p1s4"><span id="Michael">Michael</span></span>
<span id="p1s5"><span id="Dahl">Dahl</span></span> <br />
<span id="p1s6"><span id="art">art</span></span>
<span id="p1s7"><span id="by1">by</span></span> <br />
<span id="p1s9"><span id="Zoe">Zoe</span></span>
<span id="p1s10"><span id="Persico">Persico</span></span> <br />
</p>
</section>
</div>
</body>
<smil xmlns="http://www.w3.org/ns/SMIL" xmlns:epub="http://www.idpf.org/2007/ops" version="3.0">
<body>
<par id="id0">
<text src="../P1.xhtml#Me">
</text>
<audio clipBegin="4.15" clipEnd="4.479" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id1">
<text src="../P1.xhtml#and">
</text>
<audio clipBegin="4.479" clipEnd="4.674" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id2">
<text src="../P1.xhtml#My">
</text>
<audio clipBegin="4.674" clipEnd="4.895" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id3">
<text src="../P1.xhtml#Cat">
</text>
<audio clipBegin="4.895" clipEnd="5.31" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id4">
<text src="../P1.xhtml#by">
</text>
<audio clipBegin="5.76" clipEnd="5.919" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id5">
<text src="../P1.xhtml#Michael">
</text>
<audio clipBegin="5.919" clipEnd="6.37" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
<par id="id6">
<text src="../P1.xhtml#Dahl">
</text>
<audio clipBegin="6.371" clipEnd="6.778" src="../audio/mmp_mecat_f16_masteraudio.mp3">
</audio>
</par>
</body>
</smil>
</html>

View file

@ -2,20 +2,20 @@ import { qs, qsa } from "./core";
import Url from "./url";
import Path from "./path";
export function replaceBase(doc, section){
export function replaceBase(doc, section) {
var base;
var head;
var url = section.url;
var absolute = (url.indexOf("://") > -1);
if(!doc){
if (!doc) {
return;
}
head = qs(doc, "head");
base = qs(head, "base");
if(!base) {
if (!base) {
base = doc.createElement("base");
head.insertBefore(base, head.firstChild);
}
@ -28,12 +28,12 @@ export function replaceBase(doc, section){
base.setAttribute("href", url);
}
export function replaceCanonical(doc, section){
export function replaceCanonical(doc, section) {
var head;
var link;
var url = section.canonical;
if(!doc){
if (!doc) {
return;
}
@ -50,11 +50,11 @@ export function replaceCanonical(doc, section){
}
}
export function replaceMeta(doc, section){
export function replaceMeta(doc, section) {
var head;
var meta;
var id = section.idref;
if(!doc){
if (!doc) {
return;
}
@ -79,35 +79,34 @@ export function replaceLinks(contents, fn) {
if (!links.length) {
return;
}
var base = qs(contents.ownerDocument, "base");
var location = base ? base.getAttribute("href") : undefined;
var replaceLink = function(link){
var replaceLink = function (link) {
var href = link.getAttribute("href");
if(href.indexOf("mailto:") === 0){
if (href.indexOf("mailto:") === 0) {
return;
}
var absolute = (href.indexOf("://") > -1);
if(absolute){
if (absolute) {
link.setAttribute("target", "_blank");
}else{
} else {
var linkUrl;
try {
linkUrl = new Url(href, location);
} catch(error) {
} catch (error) {
// NOOP
}
link.onclick = function(){
link.onclick = function () {
if(linkUrl && linkUrl.hash) {
if (linkUrl && linkUrl.hash) {
fn(linkUrl.Path.path + linkUrl.hash);
} else if(linkUrl){
} else if (linkUrl) {
fn(linkUrl.Path.path);
} else {
fn(href);
@ -126,7 +125,7 @@ export function replaceLinks(contents, fn) {
}
export function substitute(content, urls, replacements) {
urls.forEach(function(url, i){
urls.forEach(function (url, i) {
if (url && replacements[i]) {
// Account for special characters in the file name.
// See https://stackoverflow.com/a/6318729.