1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-04 15:09:16 +02:00

Add RTL support

This commit is contained in:
Fred Chasen 2017-10-16 17:24:45 -07:00
parent 8999feb613
commit 9b6e864ae4
10 changed files with 380 additions and 111 deletions

View file

@ -33,17 +33,18 @@
rendition.display(currentSectionIndex);
var title = document.getElementById("title");
book.ready.then(() => {
var next = document.getElementById("next");
next.addEventListener("click", function(e){
rendition.next();
book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next();
e.preventDefault();
}, false);
var prev = document.getElementById("prev");
prev.addEventListener("click", function(e){
rendition.prev();
book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev();
e.preventDefault();
}, false);
@ -51,12 +52,12 @@
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
rendition.next();
book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next();
}
};
@ -64,12 +65,11 @@
rendition.on("keyup", keyListener);
document.addEventListener("keyup", keyListener, false);
})
var title = document.getElementById("title");
rendition.on("rendered", function(section){
var nextSection = section.next();
var prevSection = section.prev();
var current = book.navigation && book.navigation.get(section.href);
if (current) {
@ -88,22 +88,26 @@
}
}
if(nextSection) {
next.textContent = "";
} else {
next.textContent = "";
}
if(prevSection) {
prev.textContent = "";
} else {
prev.textContent = "";
}
});
rendition.on("relocated", function(location){
console.log(location);
var next = book.package.metadata.direction === "rtl" ? document.getElementById("prev") : document.getElementById("next");
var prev = book.package.metadata.direction === "rtl" ? document.getElementById("next") : document.getElementById("prev");
if (location.atEnd) {
next.style.visibility = "hidden";
} else {
next.style.visibility = "visible";
}
if (location.atStart) {
prev.style.visibility = "hidden";
} else {
prev.style.visibility = "visible";
}
});
window.addEventListener("unload", function () {

View file

@ -332,6 +332,7 @@ class Contents {
resizeCheck() {
let width = this.textWidth();
let height = this.textHeight();
if (width != this._size.width || height != this._size.height) {
this._size = {
@ -763,7 +764,7 @@ class Contents {
var COLUMN_WIDTH = prefixed("column-width");
var COLUMN_FILL = prefixed("column-fill");
this.width(width);
this.width("100%");
this.height(height);
// Deal with Mobile trying to scale to viewport
@ -819,6 +820,12 @@ class Contents {
this.css("background-color", "transparent");
}
direction(dir) {
if (this.documentElement) {
this.documentElement.style["direction"] = dir;
}
}
mapPage(cfiBase, layout, start, end, dev) {
var mapping = new Mapping(layout, dev);

View file

@ -426,13 +426,14 @@ class ContinuousViewManager extends DefaultViewManager {
onScroll(){
let scrollTop;
let scrollLeft;
let dir = this.settings.direction === "rtl" ? -1 : 1;
if(this.settings.height) {
scrollTop = this.container.scrollTop;
scrollLeft = this.container.scrollLeft;
} else {
scrollTop = window.scrollY;
scrollLeft = window.scrollX;
scrollTop = window.scrollY * dir;
scrollLeft = window.scrollX * dir;
}
this.scrollTop = scrollTop;

View file

@ -36,6 +36,8 @@ class DefaultViewManager {
height: 0
};
this.rendered = false;
}
render(element, size){
@ -60,7 +62,8 @@ class DefaultViewManager {
overflow: this.overflow,
hidden: this.settings.hidden,
axis: this.settings.axis,
fullsize: this.fullsize
fullsize: this.fullsize,
direction: this.settings.direction
});
this.stage.attachTo(element);
@ -93,6 +96,9 @@ class DefaultViewManager {
if (this.layout) {
this.updateLayout();
}
this.rendered = true;
}
addEventListeners(){
@ -134,6 +140,8 @@ class DefaultViewManager {
this.stage.destroy();
this.rendered = false;
/*
clearTimeout(this.trimTimeout);
@ -227,7 +235,13 @@ class DefaultViewManager {
// View is already shown, just move to correct location in view
if(visible && section) {
let offset = visible.offset();
if (this.settings.direction === "ltr") {
this.scrollTo(offset.left, offset.top, true);
} else {
let width = view.width();
this.scrollTo(offset.left + width, offset.top, true);
}
if(target) {
let offset = visible.locationOf(target);
@ -244,6 +258,16 @@ class DefaultViewManager {
this.add(section)
.then(function(view){
// if (this.settings.direction === "rtl") {
// let offset = view.offset();
// let width = view.width();
//
// this.moveTo({
// left: offset.left + width,
// top: offset.top
// });
// }
// Move to correct place within the section, if needed
if(target) {
let offset = view.locationOf(target);
@ -331,6 +355,10 @@ class DefaultViewManager {
prepend(section){
var view = this.createView(section);
view.on("resized", (bounds) => {
this.counter(bounds);
});
this.views.prepend(view);
view.onDisplayed = this.afterDisplayed.bind(this);
@ -338,6 +366,16 @@ class DefaultViewManager {
return view.display(this.request);
}
counter(bounds){
if(this.settings.axis === "vertical") {
this.scrollBy(0, bounds.heightDelta, true);
} else {
this.scrollBy(bounds.widthDelta, 0, true);
}
}
// resizeView(view) {
//
// if(this.settings.globalLayoutProperties.layout === "pre-paginated") {
@ -352,22 +390,35 @@ class DefaultViewManager {
var next;
var left;
let dir = this.settings.direction;
if(!this.views.length) return;
if(this.settings.axis === "horizontal") {
if(this.settings.axis === "horizontal" && (!dir || dir === "ltr")) {
this.scrollLeft = this.container.scrollLeft;
left = this.container.scrollLeft + this.container.offsetWidth + this.layout.delta;
left = this.container.scrollLeft + this.container.offsetWidth + this.layout.delta; // 450 off
if(left <= this.container.scrollWidth) {
this.scrollBy(this.layout.delta, 0, true);
} else if (left - this.layout.columnWidth === this.container.scrollWidth) {
} else if ((left - this.layout.pageWidth) === this.container.scrollWidth) {
this.scrollTo(this.container.scrollWidth - this.layout.delta, 0, true);
next = this.views.last().section.next();
} else {
next = this.views.last().section.next();
}
} else if (this.settings.axis === "horizontal" && dir === "rtl") {
this.scrollLeft = this.container.scrollLeft;
left = this.container.scrollLeft;
if(left > 0) {
this.scrollBy(-this.layout.delta, 0, true);
} else {
next = this.views.last().section.next();
}
} else {
next = this.views.last().section.next();
}
@ -396,10 +447,11 @@ class DefaultViewManager {
prev(){
var prev;
var left;
let dir = this.settings.direction;
if(!this.views.length) return;
if(this.settings.axis === "horizontal") {
if(this.settings.axis === "horizontal" && (!dir || dir === "ltr")) {
this.scrollLeft = this.container.scrollLeft;
@ -411,6 +463,19 @@ class DefaultViewManager {
prev = this.views.first().section.prev();
}
} else if (this.settings.axis === "horizontal" && dir === "rtl") {
this.scrollLeft = this.container.scrollLeft;
left = this.container.scrollLeft + this.container.offsetWidth + this.layout.delta;
if(left <= this.container.scrollWidth) {
this.scrollBy(this.layout.delta, 0, true);
} else if ((left - this.layout.pageWidth) === this.container.scrollWidth) {
this.scrollTo(this.container.scrollWidth - this.layout.delta, 0, true);
} else {
prev = this.views.first().section.prev();
}
} else {
@ -433,8 +498,13 @@ class DefaultViewManager {
}.bind(this))
.then(function(){
if(this.settings.axis === "horizontal") {
if (this.settings.direction === "rtl") {
this.scrollTo(0, 0, true);
} else {
console.log(this.container.scrollWidth);
this.scrollTo(this.container.scrollWidth - this.layout.delta, 0, true);
}
}
this.views.show();
}.bind(this));
}
@ -543,20 +613,33 @@ class DefaultViewManager {
let mapping = this.mapping.page(view.contents, view.section.cfiBase, start, end);
// Find displayed pages
let startPos = left + used;
let endPos = startPos + this.layout.spreadWidth - used;
if (endPos > offset + width) {
endPos = offset + width;
used = this.layout.pageWidth;
}
//console.log("pre", end, offset + width);
// if (end > offset + width) {
// end = offset + width;
// used = this.layout.pageWidth;
// }
// console.log("post", end);
let totalPages = this.layout.count(width).pages;
let currPage = Math.ceil((startPos + (this.layout.gap) - offset) / this.layout.pageWidth);
let startPage = Math.floor(start / this.layout.pageWidth);
let pages = [];
let endPage = Math.ceil((endPos - (this.layout.gap) - offset) / this.layout.pageWidth);
let endPage = Math.floor(end / this.layout.pageWidth);
pages = [currPage];
for (var i = currPage; i <= endPage; i++) {
// start page should not be negative
if (startPage < 0) {
startPage = 0;
endPage = endPage + 1;
}
// Reverse page counts for rtl
if (this.settings.direction === "rtl") {
let tempStartPage = startPage;
startPage = totalPages - endPage;
endPage = totalPages - tempStartPage;
}
for (var i = startPage + 1; i <= endPage; i++) {
let pg = i;
pages.push(pg);
}
@ -615,6 +698,8 @@ class DefaultViewManager {
}
scrollBy(x, y, silent){
let dir = this.settings.direction === "rtl" ? -1 : 1;
if(silent) {
this.ignore = true;
}
@ -623,7 +708,7 @@ class DefaultViewManager {
if(x) this.container.scrollLeft += x;
if(y) this.container.scrollTop += y;
} else {
window.scrollBy(x,y);
window.scrollBy(x * dir, y * dir);
}
this.scrolled = true;
}
@ -692,7 +777,7 @@ class DefaultViewManager {
this.layout = layout;
this.updateLayout();
this.mapping = new Mapping(this.layout.props);
this.mapping = new Mapping(this.layout.props, this.settings.direction);
// this.manager.layout(this.layout.format);
}
@ -778,6 +863,20 @@ class DefaultViewManager {
});
return contents;
}
direction(dir="ltr") {
this.settings.direction = dir;
this.stage && this.stage.direction(dir);
this.viewSettings.direction = dir;
this.updateLayout();
}
isRendered() {
return this.rendered;
}
}
//-- Enable binding events to Manager

View file

@ -19,10 +19,11 @@ class Stage {
* Resizes to passed width and height or to the elements size
*/
create(options){
var height = options.height;// !== false ? options.height : "100%";
var width = options.width;// !== false ? options.width : "100%";
var overflow = options.overflow || false;
var axis = options.axis || "vertical";
let height = options.height;// !== false ? options.height : "100%";
let width = options.width;// !== false ? options.width : "100%";
let overflow = options.overflow || false;
let axis = options.axis || "vertical";
let direction = options.direction;
if(options.height && isNumber(options.height)) {
height = options.height + "px";
@ -64,6 +65,15 @@ class Stage {
container.style.overflow = overflow;
}
if (direction) {
container.dir = direction;
container.style["direction"] = direction;
}
if (direction && this.settings.fullsize) {
document.body.style["direction"] = direction;
}
return container;
}
@ -270,6 +280,17 @@ class Stage {
// this.orientation = orientation;
// }
direction(dir) {
if (this.container) {
this.container.dir = dir;
this.container.style["direction"] = dir;
}
if (this.settings.fullsize) {
document.body.style["direction"] = dir;
}
}
destroy() {
var base;

View file

@ -9,6 +9,7 @@ class IframeView {
this.settings = extend({
ignoreClass : "",
axis: "vertical",
direction: "ltr",
width: 0,
height: 0,
layout: undefined,

View file

@ -1,9 +1,10 @@
import EpubCFI from "./epubcfi";
class Mapping {
constructor(layout, dev) {
constructor(layout, direction, dev) {
this.layout = layout;
this.horizontal = (this.layout.flow === "paginated") ? true : false;
this.direction = direction || "ltr";
this._dev = dev;
}
@ -107,7 +108,7 @@ class Mapping {
$el = stack.shift();
found = this.walk($el, (node) => {
var left, right;
var left, right, top, bottom;
var elPos;
var elRange;
@ -120,6 +121,8 @@ class Mapping {
elPos = node.getBoundingClientRect();
}
if (this.horizontal && this.direction === "ltr") {
left = this.horizontal ? elPos.left : elPos.top;
right = this.horizontal ? elPos.right : elPos.bottom;
@ -132,6 +135,37 @@ class Mapping {
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = elPos.left;
right = elPos.right;
if( right <= end && right >= start ) {
return node;
} else if (left < end) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = elPos.top;
bottom = elPos.bottom;
if( top >= start && top <= end ) {
return node;
} else if (bottom > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
if(found) {
@ -156,7 +190,7 @@ class Mapping {
found = this.walk($el, (node) => {
var left, right;
var left, right, top, bottom;
var elPos;
var elRange;
@ -169,8 +203,10 @@ class Mapping {
elPos = node.getBoundingClientRect();
}
left = Math.round(this.horizontal ? elPos.left : elPos.top);
right = Math.round(this.horizontal ? elPos.right : elPos.bottom);
if (this.horizontal && this.direction === "ltr") {
left = Math.round(elPos.left);
right = Math.round(elPos.right);
if(left > end && $prev) {
return $prev;
@ -181,6 +217,36 @@ class Mapping {
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = Math.round(this.horizontal ? elPos.left : elPos.top);
right = Math.round(this.horizontal ? elPos.right : elPos.bottom);
if(right < start && $prev) {
return $prev;
} else if(left < start) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = Math.round(elPos.top);
bottom = Math.round(elPos.bottom);
if(top > end && $prev) {
return $prev;
} else if(bottom > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
@ -199,18 +265,36 @@ class Mapping {
var ranges = this.splitTextNodeIntoRanges(node);
var range;
var pos;
var left;
var left, top, right;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
left = this.horizontal ? pos.left : pos.top;
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
if( left >= start ) {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
right = pos.right;
if( right <= end ) {
return range;
}
} else {
top = pos.top;
if( top >= start ) {
return range;
}
}
// prev = range;
}
@ -223,14 +307,17 @@ class Mapping {
var prev;
var range;
var pos;
var left, right;
var left, right, top, bottom;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
left = this.horizontal ? pos.left : pos.top;
right = this.horizontal ? pos.right : pos.bottom;
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
right = pos.right;
if(left > end && prev) {
return prev;
@ -238,6 +325,31 @@ class Mapping {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
left = pos.left
right = pos.right;
if(right < start && prev) {
return prev;
} else if(left < start) {
return range;
}
} else {
top = pos.top;
bottom = pos.bottom;
if(top > end && prev) {
return prev;
} else if(bottom > end) {
return range;
}
}
prev = range;
}

View file

@ -9,7 +9,7 @@ class Navigation {
this.toc = [];
this.tocByHref = {};
this.tocById = {};
this.length = 0;
if (xml) {
this.parse(xml);
}
@ -54,6 +54,7 @@ class Navigation {
this.tocById[item.id] = i;
}
this.length = toc.length;
}
/**

View file

@ -4,7 +4,7 @@ import Hook from "./utils/hook";
import EpubCFI from "./epubcfi";
import Queue from "./utils/queue";
import Layout from "./layout";
import Mapping from "./mapping";
// import Mapping from "./mapping";
import Themes from "./themes";
import Contents from "./contents";
import Annotations from "./annotations";
@ -164,6 +164,8 @@ class Rendition {
});
}
this.direction(this.book.package.metadata.direction);
// Parse metadata to get layout props
this.settings.globalLayoutProperties = this.determineLayoutProperties(this.book.package.metadata);
@ -248,9 +250,9 @@ class Rendition {
this.displaying = displaying;
// Check if this is a book percentage
if (this.book.locations.length &&
if (this.book.locations.length() &&
(isFloat(target) ||
(typeof target === "string" && target == parseFloat(target))) // Handle 1.0
(target === "1.0")) // Handle 1.0
) {
target = this.book.locations.cfiFromPercentage(parseFloat(target));
}
@ -426,7 +428,8 @@ class Rendition {
var viewport = metadata.viewport || "";
var minSpreadWidth = this.settings.minSpreadWidth || metadata.minSpreadWidth || 800;
if (this.settings.width >= 0 && this.settings.height >= 0) {
if ((this.settings.width === 0 || this.settings.width > 0) &&
(this.settings.height === 0 || this.settings.height > 0)) {
viewport = "width="+this.settings.width+", height="+this.settings.height+"";
}
@ -481,7 +484,7 @@ class Rendition {
this.manager.updateFlow(_flow);
}
if (this.location) {
if (this.manager && this.manager.isRendered() && this.location) {
this.manager.clear();
this.display(this.location.start.cfi);
}
@ -496,7 +499,7 @@ class Rendition {
this._layout = new Layout(settings);
this._layout.spread(settings.spread, this.settings.minSpreadWidth);
this.mapping = new Mapping(this._layout.props);
// this.mapping = new Mapping(this._layout.props);
}
if (this.manager && this._layout) {
@ -520,6 +523,25 @@ class Rendition {
}
}
/**
* Adjust the flow of the rendition to paginated or scrolled
* (scrolled-continuous vs scrolled-doc are handled by different view managers)
* @param {string} flow
*/
direction(dir){
this.settings.direction = dir || "ltr";
if (this.manager) {
this.manager.direction(this.settings.direction);
}
if (this.manager && this.manager.isRendered() && this.location) {
this.manager.clear();
this.display(this.location.start.cfi);
}
}
/**
* Report the current location
* @private

View file

@ -35,7 +35,8 @@ export function isNumber(n) {
}
export function isFloat(n) {
return isNumber(n) && (Math.floor(n) !== n);
let f = parseFloat(n);
return f === n && isNumber(n) && (Math.floor(f) !== n);
}
export function prefixed(unprefixed) {