summaryrefslogtreecommitdiff
path: root/lib/epub.js/src/managers
diff options
context:
space:
mode:
Diffstat (limited to 'lib/epub.js/src/managers')
-rw-r--r--lib/epub.js/src/managers/continuous/index.js588
-rw-r--r--lib/epub.js/src/managers/default/index.js1073
-rw-r--r--lib/epub.js/src/managers/helpers/snap.js338
-rw-r--r--lib/epub.js/src/managers/helpers/stage.js363
-rw-r--r--lib/epub.js/src/managers/helpers/views.js167
-rw-r--r--lib/epub.js/src/managers/views/iframe.js835
-rw-r--r--lib/epub.js/src/managers/views/inline.js432
7 files changed, 3796 insertions, 0 deletions
diff --git a/lib/epub.js/src/managers/continuous/index.js b/lib/epub.js/src/managers/continuous/index.js
new file mode 100644
index 0000000..e6a9e61
--- /dev/null
+++ b/lib/epub.js/src/managers/continuous/index.js
@@ -0,0 +1,588 @@
+import {extend, defer, requestAnimationFrame} from "../../utils/core";
+import DefaultViewManager from "../default";
+import Snap from "../helpers/snap";
+import { EVENTS } from "../../utils/constants";
+import debounce from "lodash/debounce";
+
+class ContinuousViewManager extends DefaultViewManager {
+ constructor(options) {
+ super(options);
+
+ this.name = "continuous";
+
+ this.settings = extend(this.settings || {}, {
+ infinite: true,
+ overflow: undefined,
+ axis: undefined,
+ writingMode: undefined,
+ flow: "scrolled",
+ offset: 500,
+ offsetDelta: 250,
+ width: undefined,
+ height: undefined,
+ snap: false,
+ afterScrolledTimeout: 10
+ });
+
+ extend(this.settings, options.settings || {});
+
+ // Gap can be 0, but defaults doesn't handle that
+ if (options.settings.gap != "undefined" && options.settings.gap === 0) {
+ this.settings.gap = options.settings.gap;
+ }
+
+ this.viewSettings = {
+ ignoreClass: this.settings.ignoreClass,
+ axis: this.settings.axis,
+ flow: this.settings.flow,
+ layout: this.layout,
+ width: 0,
+ height: 0,
+ forceEvenPages: false
+ };
+
+ this.scrollTop = 0;
+ this.scrollLeft = 0;
+ }
+
+ display(section, target){
+ return DefaultViewManager.prototype.display.call(this, section, target)
+ .then(function () {
+ return this.fill();
+ }.bind(this));
+ }
+
+ fill(_full){
+ var full = _full || new defer();
+
+ this.q.enqueue(() => {
+ return this.check();
+ }).then((result) => {
+ if (result) {
+ this.fill(full);
+ } else {
+ full.resolve();
+ }
+ });
+
+ return full.promise;
+ }
+
+ moveTo(offset){
+ // var bounds = this.stage.bounds();
+ // var dist = Math.floor(offset.top / bounds.height) * bounds.height;
+ var distX = 0,
+ distY = 0;
+
+ var offsetX = 0,
+ offsetY = 0;
+
+ if(!this.isPaginated) {
+ distY = offset.top;
+ offsetY = offset.top+this.settings.offsetDelta;
+ } else {
+ distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
+ offsetX = distX+this.settings.offsetDelta;
+ }
+
+ if (distX > 0 || distY > 0) {
+ this.scrollBy(distX, distY, true);
+ }
+ }
+
+ afterResized(view){
+ this.emit(EVENTS.MANAGERS.RESIZE, view.section);
+ }
+
+ // Remove Previous Listeners if present
+ removeShownListeners(view){
+
+ // view.off("shown", this.afterDisplayed);
+ // view.off("shown", this.afterDisplayedAbove);
+ view.onDisplayed = function(){};
+
+ }
+
+ add(section){
+ var view = this.createView(section);
+
+ this.views.append(view);
+
+ view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
+ view.expanded = true;
+ });
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ // view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
+ view.onDisplayed = this.afterDisplayed.bind(this);
+ view.onResize = this.afterResized.bind(this);
+
+ return view.display(this.request);
+ }
+
+ append(section){
+ var view = this.createView(section);
+
+ view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
+ view.expanded = true;
+ });
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ this.views.append(view);
+
+ view.onDisplayed = this.afterDisplayed.bind(this);
+
+ return view;
+ }
+
+ prepend(section){
+ var view = this.createView(section);
+
+ view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
+ this.counter(bounds);
+ view.expanded = true;
+ });
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ this.views.prepend(view);
+
+ view.onDisplayed = this.afterDisplayed.bind(this);
+
+ return view;
+ }
+
+ counter(bounds){
+ if(this.settings.axis === "vertical") {
+ this.scrollBy(0, bounds.heightDelta, true);
+ } else {
+ this.scrollBy(bounds.widthDelta, 0, true);
+ }
+ }
+
+ update(_offset){
+ var container = this.bounds();
+ var views = this.views.all();
+ var viewsLength = views.length;
+ var visible = [];
+ var offset = typeof _offset != "undefined" ? _offset : (this.settings.offset || 0);
+ var isVisible;
+ var view;
+
+ var updating = new defer();
+ var promises = [];
+ for (var i = 0; i < viewsLength; i++) {
+ view = views[i];
+
+ isVisible = this.isVisible(view, offset, offset, container);
+
+ if(isVisible === true) {
+ // console.log("visible " + view.index, view.displayed);
+
+ if (!view.displayed) {
+ let displayed = view.display(this.request)
+ .then(function (view) {
+ view.show();
+ }, (err) => {
+ view.hide();
+ });
+ promises.push(displayed);
+ } else {
+ view.show();
+ }
+ visible.push(view);
+ } else {
+ this.q.enqueue(view.destroy.bind(view));
+ // console.log("hidden " + view.index, view.displayed);
+
+ clearTimeout(this.trimTimeout);
+ this.trimTimeout = setTimeout(function(){
+ this.q.enqueue(this.trim.bind(this));
+ }.bind(this), 250);
+ }
+
+ }
+
+ if(promises.length){
+ return Promise.all(promises)
+ .catch((err) => {
+ updating.reject(err);
+ });
+ } else {
+ updating.resolve();
+ return updating.promise;
+ }
+
+ }
+
+ check(_offsetLeft, _offsetTop){
+ var checking = new defer();
+ var newViews = [];
+
+ var horizontal = (this.settings.axis === "horizontal");
+ var delta = this.settings.offset || 0;
+
+ if (_offsetLeft && horizontal) {
+ delta = _offsetLeft;
+ }
+
+ if (_offsetTop && !horizontal) {
+ delta = _offsetTop;
+ }
+
+ var bounds = this._bounds; // bounds saved this until resize
+
+ let offset = horizontal ? this.scrollLeft : this.scrollTop;
+ let visibleLength = horizontal ? Math.floor(bounds.width) : bounds.height;
+ let contentLength = horizontal ? this.container.scrollWidth : this.container.scrollHeight;
+ let writingMode = (this.writingMode && this.writingMode.indexOf("vertical") === 0) ? "vertical" : "horizontal";
+ let rtlScrollType = this.settings.rtlScrollType;
+ let rtl = this.settings.direction === "rtl";
+
+ if (!this.settings.fullsize) {
+ // Scroll offset starts at width of element
+ if (rtl && rtlScrollType === "default" && writingMode === "horizontal") {
+ offset = contentLength - visibleLength - offset;
+ }
+ // Scroll offset starts at 0 and goes negative
+ if (rtl && rtlScrollType === "negative" && writingMode === "horizontal") {
+ offset = offset * -1;
+ }
+ } else {
+ // Scroll offset starts at 0 and goes negative
+ if ((horizontal && rtl && rtlScrollType === "negative") ||
+ (!horizontal && rtl && rtlScrollType === "default")) {
+ offset = offset * -1;
+ }
+ }
+
+ let prepend = () => {
+ let first = this.views.first();
+ let prev = first && first.section.prev();
+
+ if(prev) {
+ newViews.push(this.prepend(prev));
+ }
+ };
+
+ let append = () => {
+ let last = this.views.last();
+ let next = last && last.section.next();
+
+ if(next) {
+ newViews.push(this.append(next));
+ }
+
+ };
+
+ let end = offset + visibleLength + delta;
+ let start = offset - delta;
+
+ if (end >= contentLength) {
+ append();
+ }
+
+ if (start < 0) {
+ prepend();
+ }
+
+
+ let promises = newViews.map((view) => {
+ return view.display(this.request);
+ });
+
+ if(newViews.length){
+ return Promise.all(promises)
+ .then(() => {
+ return this.check();
+ })
+ .then(() => {
+ // Check to see if anything new is on screen after rendering
+ return this.update(delta);
+ }, (err) => {
+ return err;
+ });
+ } else {
+ this.q.enqueue(function(){
+ this.update();
+ }.bind(this));
+ checking.resolve(false);
+ return checking.promise;
+ }
+
+
+ }
+
+ trim(){
+ var task = new defer();
+ var displayed = this.views.displayed();
+ var first = displayed[0];
+ var last = displayed[displayed.length-1];
+ var firstIndex = this.views.indexOf(first);
+ var lastIndex = this.views.indexOf(last);
+ var above = this.views.slice(0, firstIndex);
+ var below = this.views.slice(lastIndex+1);
+
+ // Erase all but last above
+ for (var i = 0; i < above.length-1; i++) {
+ this.erase(above[i], above);
+ }
+
+ // Erase all except first below
+ for (var j = 1; j < below.length; j++) {
+ this.erase(below[j]);
+ }
+
+ task.resolve();
+ return task.promise;
+ }
+
+ erase(view, above){ //Trim
+
+ var prevTop;
+ var prevLeft;
+
+ if(!this.settings.fullsize) {
+ prevTop = this.container.scrollTop;
+ prevLeft = this.container.scrollLeft;
+ } else {
+ prevTop = window.scrollY;
+ prevLeft = window.scrollX;
+ }
+
+ var bounds = view.bounds();
+
+ this.views.remove(view);
+
+ if(above) {
+ if (this.settings.axis === "vertical") {
+ this.scrollTo(0, prevTop - bounds.height, true);
+ } else {
+ if(this.settings.direction === 'rtl') {
+ if (!this.settings.fullsize) {
+ this.scrollTo(prevLeft, 0, true);
+ } else {
+ this.scrollTo(prevLeft + Math.floor(bounds.width), 0, true);
+ }
+ } else {
+ this.scrollTo(prevLeft - Math.floor(bounds.width), 0, true);
+ }
+ }
+ }
+
+ }
+
+ addEventListeners(stage){
+
+ window.addEventListener("unload", function(e){
+ this.ignore = true;
+ // this.scrollTo(0,0);
+ this.destroy();
+ }.bind(this));
+
+ this.addScrollListeners();
+
+ if (this.isPaginated && this.settings.snap) {
+ this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === "object") && this.settings.snap);
+ }
+ }
+
+ addScrollListeners() {
+ var scroller;
+
+ this.tick = requestAnimationFrame;
+
+ let dir = this.settings.direction === "rtl" && this.settings.rtlScrollType === "default" ? -1 : 1;
+
+ this.scrollDeltaVert = 0;
+ this.scrollDeltaHorz = 0;
+
+ if(!this.settings.fullsize) {
+ scroller = this.container;
+ this.scrollTop = this.container.scrollTop;
+ this.scrollLeft = this.container.scrollLeft;
+ } else {
+ scroller = window;
+ this.scrollTop = window.scrollY * dir;
+ this.scrollLeft = window.scrollX * dir;
+ }
+
+ this._onScroll = this.onScroll.bind(this);
+ scroller.addEventListener("scroll", this._onScroll);
+ this._scrolled = debounce(this.scrolled.bind(this), 30);
+ // this.tick.call(window, this.onScroll.bind(this));
+
+ this.didScroll = false;
+
+ }
+
+ removeEventListeners(){
+ var scroller;
+
+ if(!this.settings.fullsize) {
+ scroller = this.container;
+ } else {
+ scroller = window;
+ }
+
+ scroller.removeEventListener("scroll", this._onScroll);
+ this._onScroll = undefined;
+ }
+
+ onScroll(){
+ let scrollTop;
+ let scrollLeft;
+ let dir = this.settings.direction === "rtl" && this.settings.rtlScrollType === "default" ? -1 : 1;
+
+ if(!this.settings.fullsize) {
+ scrollTop = this.container.scrollTop;
+ scrollLeft = this.container.scrollLeft;
+ } else {
+ scrollTop = window.scrollY * dir;
+ scrollLeft = window.scrollX * dir;
+ }
+
+ this.scrollTop = scrollTop;
+ this.scrollLeft = scrollLeft;
+
+ if(!this.ignore) {
+
+ this._scrolled();
+
+ } else {
+ this.ignore = false;
+ }
+
+ this.scrollDeltaVert += Math.abs(scrollTop-this.prevScrollTop);
+ this.scrollDeltaHorz += Math.abs(scrollLeft-this.prevScrollLeft);
+
+ this.prevScrollTop = scrollTop;
+ this.prevScrollLeft = scrollLeft;
+
+ clearTimeout(this.scrollTimeout);
+ this.scrollTimeout = setTimeout(function(){
+ this.scrollDeltaVert = 0;
+ this.scrollDeltaHorz = 0;
+ }.bind(this), 150);
+
+ clearTimeout(this.afterScrolled);
+
+ this.didScroll = false;
+
+ }
+
+ scrolled() {
+
+ this.q.enqueue(function() {
+ return this.check();
+ }.bind(this));
+
+ this.emit(EVENTS.MANAGERS.SCROLL, {
+ top: this.scrollTop,
+ left: this.scrollLeft
+ });
+
+ clearTimeout(this.afterScrolled);
+ this.afterScrolled = setTimeout(function () {
+
+ // Don't report scroll if we are about the snap
+ if (this.snapper && this.snapper.supportsTouch && this.snapper.needsSnap()) {
+ return;
+ }
+
+ this.emit(EVENTS.MANAGERS.SCROLLED, {
+ top: this.scrollTop,
+ left: this.scrollLeft
+ });
+
+ }.bind(this), this.settings.afterScrolledTimeout);
+ }
+
+ next(){
+
+ let delta = this.layout.props.name === "pre-paginated" &&
+ this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta;
+
+ if(!this.views.length) return;
+
+ if(this.isPaginated && this.settings.axis === "horizontal") {
+
+ this.scrollBy(delta, 0, true);
+
+ } else {
+
+ this.scrollBy(0, this.layout.height, true);
+
+ }
+
+ this.q.enqueue(function() {
+ return this.check();
+ }.bind(this));
+ }
+
+ prev(){
+
+ let delta = this.layout.props.name === "pre-paginated" &&
+ this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta;
+
+ if(!this.views.length) return;
+
+ if(this.isPaginated && this.settings.axis === "horizontal") {
+
+ this.scrollBy(-delta, 0, true);
+
+ } else {
+
+ this.scrollBy(0, -this.layout.height, true);
+
+ }
+
+ this.q.enqueue(function() {
+ return this.check();
+ }.bind(this));
+ }
+
+ updateFlow(flow){
+ if (this.rendered && this.snapper) {
+ this.snapper.destroy();
+ this.snapper = undefined;
+ }
+
+ super.updateFlow(flow, "scroll");
+
+ if (this.rendered && this.isPaginated && this.settings.snap) {
+ this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === "object") && this.settings.snap);
+ }
+ }
+
+ destroy(){
+ super.destroy();
+
+ if (this.snapper) {
+ this.snapper.destroy();
+ }
+ }
+
+}
+
+export default ContinuousViewManager;
diff --git a/lib/epub.js/src/managers/default/index.js b/lib/epub.js/src/managers/default/index.js
new file mode 100644
index 0000000..b24a4f6
--- /dev/null
+++ b/lib/epub.js/src/managers/default/index.js
@@ -0,0 +1,1073 @@
+import EventEmitter from "event-emitter";
+import {extend, defer, windowBounds, isNumber} from "../../utils/core";
+import scrollType from "../../utils/scrolltype";
+import Mapping from "../../mapping";
+import Queue from "../../utils/queue";
+import Stage from "../helpers/stage";
+import Views from "../helpers/views";
+import { EVENTS } from "../../utils/constants";
+
+class DefaultViewManager {
+ constructor(options) {
+
+ this.name = "default";
+ this.optsSettings = options.settings;
+ this.View = options.view;
+ this.request = options.request;
+ this.renditionQueue = options.queue;
+ this.q = new Queue(this);
+
+ this.settings = extend(this.settings || {}, {
+ infinite: true,
+ hidden: false,
+ width: undefined,
+ height: undefined,
+ axis: undefined,
+ writingMode: undefined,
+ flow: "scrolled",
+ ignoreClass: "",
+ fullsize: undefined
+ });
+
+ extend(this.settings, options.settings || {});
+
+ this.viewSettings = {
+ ignoreClass: this.settings.ignoreClass,
+ axis: this.settings.axis,
+ flow: this.settings.flow,
+ layout: this.layout,
+ method: this.settings.method, // srcdoc, blobUrl, write
+ width: 0,
+ height: 0,
+ forceEvenPages: true
+ };
+
+ this.rendered = false;
+
+ }
+
+ render(element, size){
+ let tag = element.tagName;
+
+ if (typeof this.settings.fullsize === "undefined" &&
+ tag && (tag.toLowerCase() == "body" ||
+ tag.toLowerCase() == "html")) {
+ this.settings.fullsize = true;
+ }
+
+ if (this.settings.fullsize) {
+ this.settings.overflow = "visible";
+ this.overflow = this.settings.overflow;
+ }
+
+ this.settings.size = size;
+
+ this.settings.rtlScrollType = scrollType();
+
+ // Save the stage
+ this.stage = new Stage({
+ width: size.width,
+ height: size.height,
+ overflow: this.overflow,
+ hidden: this.settings.hidden,
+ axis: this.settings.axis,
+ fullsize: this.settings.fullsize,
+ direction: this.settings.direction
+ });
+
+ this.stage.attachTo(element);
+
+ // Get this stage container div
+ this.container = this.stage.getContainer();
+
+ // Views array methods
+ this.views = new Views(this.container);
+
+ // Calculate Stage Size
+ this._bounds = this.bounds();
+ this._stageSize = this.stage.size();
+
+ // Set the dimensions for views
+ this.viewSettings.width = this._stageSize.width;
+ this.viewSettings.height = this._stageSize.height;
+
+ // Function to handle a resize event.
+ // Will only attach if width and height are both fixed.
+ this.stage.onResize(this.onResized.bind(this));
+
+ this.stage.onOrientationChange(this.onOrientationChange.bind(this));
+
+ // Add Event Listeners
+ this.addEventListeners();
+
+ // Add Layout method
+ // this.applyLayoutMethod();
+ if (this.layout) {
+ this.updateLayout();
+ }
+
+ this.rendered = true;
+
+ }
+
+ addEventListeners(){
+ var scroller;
+
+ window.addEventListener("unload", function(e){
+ this.destroy();
+ }.bind(this));
+
+ if(!this.settings.fullsize) {
+ scroller = this.container;
+ } else {
+ scroller = window;
+ }
+
+ this._onScroll = this.onScroll.bind(this);
+ scroller.addEventListener("scroll", this._onScroll);
+ }
+
+ removeEventListeners(){
+ var scroller;
+
+ if(!this.settings.fullsize) {
+ scroller = this.container;
+ } else {
+ scroller = window;
+ }
+
+ scroller.removeEventListener("scroll", this._onScroll);
+ this._onScroll = undefined;
+ }
+
+ destroy(){
+ clearTimeout(this.orientationTimeout);
+ clearTimeout(this.resizeTimeout);
+ clearTimeout(this.afterScrolled);
+
+ this.clear();
+
+ this.removeEventListeners();
+
+ this.stage.destroy();
+
+ this.rendered = false;
+
+ /*
+
+ clearTimeout(this.trimTimeout);
+ if(this.settings.hidden) {
+ this.element.removeChild(this.wrapper);
+ } else {
+ this.element.removeChild(this.container);
+ }
+ */
+ }
+
+ onOrientationChange(e) {
+ let {orientation} = window;
+
+ if(this.optsSettings.resizeOnOrientationChange) {
+ this.resize();
+ }
+
+ // Per ampproject:
+ // In IOS 10.3, the measured size of an element is incorrect if the
+ // element size depends on window size directly and the measurement
+ // happens in window.resize event. Adding a timeout for correct
+ // measurement. See https://github.com/ampproject/amphtml/issues/8479
+ clearTimeout(this.orientationTimeout);
+ this.orientationTimeout = setTimeout(function(){
+ this.orientationTimeout = undefined;
+
+ if(this.optsSettings.resizeOnOrientationChange) {
+ this.resize();
+ }
+
+ this.emit(EVENTS.MANAGERS.ORIENTATION_CHANGE, orientation);
+ }.bind(this), 500);
+
+ }
+
+ onResized(e) {
+ this.resize();
+ }
+
+ resize(width, height, epubcfi){
+ let stageSize = this.stage.size(width, height);
+
+ // For Safari, wait for orientation to catch up
+ // if the window is a square
+ this.winBounds = windowBounds();
+ if (this.orientationTimeout &&
+ this.winBounds.width === this.winBounds.height) {
+ // reset the stage size for next resize
+ this._stageSize = undefined;
+ return;
+ }
+
+ if (this._stageSize &&
+ this._stageSize.width === stageSize.width &&
+ this._stageSize.height === stageSize.height ) {
+ // Size is the same, no need to resize
+ return;
+ }
+
+ this._stageSize = stageSize;
+
+ this._bounds = this.bounds();
+
+ // Clear current views
+ this.clear();
+
+ // Update for new views
+ this.viewSettings.width = this._stageSize.width;
+ this.viewSettings.height = this._stageSize.height;
+
+ this.updateLayout();
+
+ this.emit(EVENTS.MANAGERS.RESIZED, {
+ width: this._stageSize.width,
+ height: this._stageSize.height
+ }, epubcfi);
+ }
+
+ createView(section, forceRight) {
+ return new this.View(section, extend(this.viewSettings, { forceRight }) );
+ }
+
+ handleNextPrePaginated(forceRight, section, action) {
+ let next;
+
+ if (this.layout.name === "pre-paginated" && this.layout.divisor > 1) {
+ if (forceRight || section.index === 0) {
+ // First page (cover) should stand alone for pre-paginated books
+ return;
+ }
+ next = section.next();
+ if (next && !next.properties.includes("page-spread-left")) {
+ return action.call(this, next);
+ }
+ }
+ }
+
+ display(section, target){
+
+ var displaying = new defer();
+ var displayed = displaying.promise;
+
+ // Check if moving to target is needed
+ if (target === section.href || isNumber(target)) {
+ target = undefined;
+ }
+
+ // Check to make sure the section we want isn't already shown
+ var visible = this.views.find(section);
+
+ // View is already shown, just move to correct location in view
+ if(visible && section && this.layout.name !== "pre-paginated") {
+ let offset = visible.offset();
+
+ if (this.settings.direction === "ltr") {
+ this.scrollTo(offset.left, offset.top, true);
+ } else {
+ let width = visible.width();
+ this.scrollTo(offset.left + width, offset.top, true);
+ }
+
+ if(target) {
+ let offset = visible.locationOf(target);
+ let width = visible.width();
+ this.moveTo(offset, width);
+ }
+
+ displaying.resolve();
+ return displayed;
+ }
+
+ // Hide all current views
+ this.clear();
+
+ let forceRight = false;
+ if (this.layout.name === "pre-paginated" && this.layout.divisor === 2 && section.properties.includes("page-spread-right")) {
+ forceRight = true;
+ }
+
+ this.add(section, forceRight)
+ .then(function(view){
+
+ // Move to correct place within the section, if needed
+ if(target) {
+ let offset = view.locationOf(target);
+ let width = view.width();
+ this.moveTo(offset, width);
+ }
+
+ }.bind(this), (err) => {
+ displaying.reject(err);
+ })
+ .then(function(){
+ return this.handleNextPrePaginated(forceRight, section, this.add);
+ }.bind(this))
+ .then(function(){
+
+ this.views.show();
+
+ displaying.resolve();
+
+ }.bind(this));
+ // .then(function(){
+ // return this.hooks.display.trigger(view);
+ // }.bind(this))
+ // .then(function(){
+ // this.views.show();
+ // }.bind(this));
+ return displayed;
+ }
+
+ afterDisplayed(view){
+ this.emit(EVENTS.MANAGERS.ADDED, view);
+ }
+
+ afterResized(view){
+ this.emit(EVENTS.MANAGERS.RESIZE, view.section);
+ }
+
+ moveTo(offset, width){
+ var distX = 0,
+ distY = 0;
+
+ if(!this.isPaginated) {
+ distY = offset.top;
+ } else {
+ distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
+
+ if (distX + this.layout.delta > this.container.scrollWidth) {
+ distX = this.container.scrollWidth - this.layout.delta;
+ }
+
+ distY = Math.floor(offset.top / this.layout.delta) * this.layout.delta;
+
+ if (distY + this.layout.delta > this.container.scrollHeight) {
+ distY = this.container.scrollHeight - this.layout.delta;
+ }
+ }
+ if(this.settings.direction === 'rtl'){
+ /***
+ the `floor` function above (L343) is on positive values, so we should add one `layout.delta`
+ to distX or use `Math.ceil` function, or multiply offset.left by -1
+ before `Math.floor`
+ */
+ distX = distX + this.layout.delta
+ distX = distX - width
+ }
+ this.scrollTo(distX, distY, true);
+ }
+
+ add(section, forceRight){
+ var view = this.createView(section, forceRight);
+
+ this.views.append(view);
+
+ // view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
+ view.onDisplayed = this.afterDisplayed.bind(this);
+ view.onResize = this.afterResized.bind(this);
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ return view.display(this.request);
+ }
+
+ append(section, forceRight){
+ var view = this.createView(section, forceRight);
+ this.views.append(view);
+
+ view.onDisplayed = this.afterDisplayed.bind(this);
+ view.onResize = this.afterResized.bind(this);
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ return view.display(this.request);
+ }
+
+ prepend(section, forceRight){
+ var view = this.createView(section, forceRight);
+
+ view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
+ this.counter(bounds);
+ });
+
+ this.views.prepend(view);
+
+ view.onDisplayed = this.afterDisplayed.bind(this);
+ view.onResize = this.afterResized.bind(this);
+
+ view.on(EVENTS.VIEWS.AXIS, (axis) => {
+ this.updateAxis(axis);
+ });
+
+ view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
+ this.updateWritingMode(mode);
+ });
+
+ 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") {
+ // view.lock("both", this.bounds.width, this.bounds.height);
+ // } else {
+ // view.lock("width", this.bounds.width, this.bounds.height);
+ // }
+ //
+ // };
+
+ next(){
+ var next;
+ var left;
+
+ let dir = this.settings.direction;
+
+ if(!this.views.length) return;
+
+ if(this.isPaginated && this.settings.axis === "horizontal" && (!dir || dir === "ltr")) {
+
+ 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 {
+ next = this.views.last().section.next();
+ }
+ } else if (this.isPaginated && this.settings.axis === "horizontal" && dir === "rtl") {
+
+ this.scrollLeft = this.container.scrollLeft;
+
+ if (this.settings.rtlScrollType === "default"){
+ left = this.container.scrollLeft;
+
+ if (left > 0) {
+ this.scrollBy(this.layout.delta, 0, true);
+ } else {
+ next = this.views.last().section.next();
+ }
+ } else {
+ left = this.container.scrollLeft + ( this.layout.delta * -1 );
+
+ if (left > this.container.scrollWidth * -1){
+ this.scrollBy(this.layout.delta, 0, true);
+ } else {
+ next = this.views.last().section.next();
+ }
+ }
+
+ } else if (this.isPaginated && this.settings.axis === "vertical") {
+
+ this.scrollTop = this.container.scrollTop;
+
+ let top = this.container.scrollTop + this.container.offsetHeight;
+
+ if(top < this.container.scrollHeight) {
+ this.scrollBy(0, this.layout.height, true);
+ } else {
+ next = this.views.last().section.next();
+ }
+
+ } else {
+ next = this.views.last().section.next();
+ }
+
+ if(next) {
+ this.clear();
+ // The new section may have a different writing-mode from the old section. Thus, we need to update layout.
+ this.updateLayout();
+
+ let forceRight = false;
+ if (this.layout.name === "pre-paginated" && this.layout.divisor === 2 && next.properties.includes("page-spread-right")) {
+ forceRight = true;
+ }
+
+ return this.append(next, forceRight)
+ .then(function(){
+ return this.handleNextPrePaginated(forceRight, next, this.append);
+ }.bind(this), (err) => {
+ return err;
+ })
+ .then(function(){
+
+ // Reset position to start for scrolled-doc vertical-rl in default mode
+ if (!this.isPaginated &&
+ this.settings.axis === "horizontal" &&
+ this.settings.direction === "rtl" &&
+ this.settings.rtlScrollType === "default") {
+
+ this.scrollTo(this.container.scrollWidth, 0, true);
+ }
+ this.views.show();
+ }.bind(this));
+ }
+
+
+ }
+
+ prev(){
+ var prev;
+ var left;
+ let dir = this.settings.direction;
+
+ if(!this.views.length) return;
+
+ if(this.isPaginated && this.settings.axis === "horizontal" && (!dir || dir === "ltr")) {
+
+ this.scrollLeft = this.container.scrollLeft;
+
+ left = this.container.scrollLeft;
+
+ if(left > 0) {
+ this.scrollBy(-this.layout.delta, 0, true);
+ } else {
+ prev = this.views.first().section.prev();
+ }
+
+ } else if (this.isPaginated && this.settings.axis === "horizontal" && dir === "rtl") {
+
+ this.scrollLeft = this.container.scrollLeft;
+
+ if (this.settings.rtlScrollType === "default"){
+ left = this.container.scrollLeft + this.container.offsetWidth;
+
+ if (left < this.container.scrollWidth) {
+ this.scrollBy(-this.layout.delta, 0, true);
+ } else {
+ prev = this.views.first().section.prev();
+ }
+ }
+ else{
+ left = this.container.scrollLeft;
+
+ if (left < 0) {
+ this.scrollBy(-this.layout.delta, 0, true);
+ } else {
+ prev = this.views.first().section.prev();
+ }
+ }
+
+ } else if (this.isPaginated && this.settings.axis === "vertical") {
+
+ this.scrollTop = this.container.scrollTop;
+
+ let top = this.container.scrollTop;
+
+ if(top > 0) {
+ this.scrollBy(0, -(this.layout.height), true);
+ } else {
+ prev = this.views.first().section.prev();
+ }
+
+ } else {
+
+ prev = this.views.first().section.prev();
+
+ }
+
+ if(prev) {
+ this.clear();
+ // The new section may have a different writing-mode from the old section. Thus, we need to update layout.
+ this.updateLayout();
+
+ let forceRight = false;
+ if (this.layout.name === "pre-paginated" && this.layout.divisor === 2 && typeof prev.prev() !== "object") {
+ forceRight = true;
+ }
+
+ return this.prepend(prev, forceRight)
+ .then(function(){
+ var left;
+ if (this.layout.name === "pre-paginated" && this.layout.divisor > 1) {
+ left = prev.prev();
+ if (left) {
+ return this.prepend(left);
+ }
+ }
+ }.bind(this), (err) => {
+ return err;
+ })
+ .then(function(){
+ if(this.isPaginated && this.settings.axis === "horizontal") {
+ if (this.settings.direction === "rtl") {
+ if (this.settings.rtlScrollType === "default"){
+ this.scrollTo(0, 0, true);
+ }
+ else{
+ this.scrollTo((this.container.scrollWidth * -1) + this.layout.delta, 0, true);
+ }
+ } else {
+ this.scrollTo(this.container.scrollWidth - this.layout.delta, 0, true);
+ }
+ }
+ this.views.show();
+ }.bind(this));
+ }
+ }
+
+ current(){
+ var visible = this.visible();
+ if(visible.length){
+ // Current is the last visible view
+ return visible[visible.length-1];
+ }
+ return null;
+ }
+
+ clear () {
+
+ // this.q.clear();
+
+ if (this.views) {
+ this.views.hide();
+ this.scrollTo(0,0, true);
+ this.views.clear();
+ }
+ }
+
+ currentLocation(){
+ this.updateLayout();
+ if (this.isPaginated && this.settings.axis === "horizontal") {
+ this.location = this.paginatedLocation();
+ } else {
+ this.location = this.scrolledLocation();
+ }
+ return this.location;
+ }
+
+ scrolledLocation() {
+ let visible = this.visible();
+ let container = this.container.getBoundingClientRect();
+ let pageHeight = (container.height < window.innerHeight) ? container.height : window.innerHeight;
+ let pageWidth = (container.width < window.innerWidth) ? container.width : window.innerWidth;
+ let vertical = (this.settings.axis === "vertical");
+ let rtl = (this.settings.direction === "rtl");
+
+ let offset = 0;
+ let used = 0;
+
+ if(this.settings.fullsize) {
+ offset = vertical ? window.scrollY : window.scrollX;
+ }
+
+ let sections = visible.map((view) => {
+ let {index, href} = view.section;
+ let position = view.position();
+ let width = view.width();
+ let height = view.height();
+
+ let startPos;
+ let endPos;
+ let stopPos;
+ let totalPages;
+
+ if (vertical) {
+ startPos = offset + container.top - position.top + used;
+ endPos = startPos + pageHeight - used;
+ totalPages = this.layout.count(height, pageHeight).pages;
+ stopPos = pageHeight;
+ } else {
+ startPos = offset + container.left - position.left + used;
+ endPos = startPos + pageWidth - used;
+ totalPages = this.layout.count(width, pageWidth).pages;
+ stopPos = pageWidth;
+ }
+
+ let currPage = Math.ceil(startPos / stopPos);
+ let pages = [];
+ let endPage = Math.ceil(endPos / stopPos);
+
+ // Reverse page counts for horizontal rtl
+ if (this.settings.direction === "rtl" && !vertical) {
+ let tempStartPage = currPage;
+ currPage = totalPages - endPage;
+ endPage = totalPages - tempStartPage;
+ }
+
+ pages = [];
+ for (var i = currPage; i <= endPage; i++) {
+ let pg = i + 1;
+ pages.push(pg);
+ }
+
+ let mapping = this.mapping.page(view.contents, view.section.cfiBase, startPos, endPos);
+
+ return {
+ index,
+ href,
+ pages,
+ totalPages,
+ mapping
+ };
+ });
+
+ return sections;
+ }
+
+ paginatedLocation(){
+ let visible = this.visible();
+ let container = this.container.getBoundingClientRect();
+
+ let left = 0;
+ let used = 0;
+
+ if(this.settings.fullsize) {
+ left = window.scrollX;
+ }
+
+ let sections = visible.map((view) => {
+ let {index, href} = view.section;
+ let offset;
+ let position = view.position();
+ let width = view.width();
+
+ // Find mapping
+ let start;
+ let end;
+ let pageWidth;
+
+ if (this.settings.direction === "rtl") {
+ offset = container.right - left;
+ pageWidth = Math.min(Math.abs(offset - position.left), this.layout.width) - used;
+ end = position.width - (position.right - offset) - used;
+ start = end - pageWidth;
+ } else {
+ offset = container.left + left;
+ pageWidth = Math.min(position.right - offset, this.layout.width) - used;
+ start = offset - position.left + used;
+ end = start + pageWidth;
+ }
+
+ used += pageWidth;
+
+ let mapping = this.mapping.page(view.contents, view.section.cfiBase, start, end);
+
+ let totalPages = this.layout.count(width).pages;
+ let startPage = Math.floor(start / this.layout.pageWidth);
+ let pages = [];
+ let endPage = Math.floor(end / this.layout.pageWidth);
+
+ // 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);
+ }
+
+ return {
+ index,
+ href,
+ pages,
+ totalPages,
+ mapping
+ };
+ });
+
+ return sections;
+ }
+
+ isVisible(view, offsetPrev, offsetNext, _container){
+ var position = view.position();
+ var container = _container || this.bounds();
+
+ if(this.settings.axis === "horizontal" &&
+ position.right > container.left - offsetPrev &&
+ position.left < container.right + offsetNext) {
+
+ return true;
+
+ } else if(this.settings.axis === "vertical" &&
+ position.bottom > container.top - offsetPrev &&
+ position.top < container.bottom + offsetNext) {
+
+ return true;
+ }
+
+ return false;
+
+ }
+
+ visible(){
+ var container = this.bounds();
+ var views = this.views.displayed();
+ var viewsLength = views.length;
+ var visible = [];
+ var isVisible;
+ var view;
+
+ for (var i = 0; i < viewsLength; i++) {
+ view = views[i];
+ isVisible = this.isVisible(view, 0, 0, container);
+
+ if(isVisible === true) {
+ visible.push(view);
+ }
+
+ }
+ return visible;
+ }
+
+ scrollBy(x, y, silent){
+ let dir = this.settings.direction === "rtl" ? -1 : 1;
+
+ if(silent) {
+ this.ignore = true;
+ }
+
+ if(!this.settings.fullsize) {
+ if(x) this.container.scrollLeft += x * dir;
+ if(y) this.container.scrollTop += y;
+ } else {
+ window.scrollBy(x * dir, y * dir);
+ }
+ this.scrolled = true;
+ }
+
+ scrollTo(x, y, silent){
+ if(silent) {
+ this.ignore = true;
+ }
+
+ if(!this.settings.fullsize) {
+ this.container.scrollLeft = x;
+ this.container.scrollTop = y;
+ } else {
+ window.scrollTo(x,y);
+ }
+ this.scrolled = true;
+ }
+
+ onScroll(){
+ let scrollTop;
+ let scrollLeft;
+
+ if(!this.settings.fullsize) {
+ scrollTop = this.container.scrollTop;
+ scrollLeft = this.container.scrollLeft;
+ } else {
+ scrollTop = window.scrollY;
+ scrollLeft = window.scrollX;
+ }
+
+ this.scrollTop = scrollTop;
+ this.scrollLeft = scrollLeft;
+
+ if(!this.ignore) {
+ this.emit(EVENTS.MANAGERS.SCROLL, {
+ top: scrollTop,
+ left: scrollLeft
+ });
+
+ clearTimeout(this.afterScrolled);
+ this.afterScrolled = setTimeout(function () {
+ this.emit(EVENTS.MANAGERS.SCROLLED, {
+ top: this.scrollTop,
+ left: this.scrollLeft
+ });
+ }.bind(this), 20);
+
+
+
+ } else {
+ this.ignore = false;
+ }
+
+ }
+
+ bounds() {
+ var bounds;
+
+ bounds = this.stage.bounds();
+
+ return bounds;
+ }
+
+ applyLayout(layout) {
+
+ this.layout = layout;
+ this.updateLayout();
+ if (this.views && this.views.length > 0 && this.layout.name === "pre-paginated") {
+ this.display(this.views.first().section);
+ }
+ // this.manager.layout(this.layout.format);
+ }
+
+ updateLayout() {
+
+ if (!this.stage) {
+ return;
+ }
+
+ this._stageSize = this.stage.size();
+
+ if(!this.isPaginated) {
+ this.layout.calculate(this._stageSize.width, this._stageSize.height);
+ } else {
+ this.layout.calculate(
+ this._stageSize.width,
+ this._stageSize.height,
+ this.settings.gap
+ );
+
+ // Set the look ahead offset for what is visible
+ this.settings.offset = this.layout.delta / this.layout.divisor;
+
+ // this.stage.addStyleRules("iframe", [{"margin-right" : this.layout.gap + "px"}]);
+
+ }
+
+ // Set the dimensions for views
+ this.viewSettings.width = this.layout.width;
+ this.viewSettings.height = this.layout.height;
+
+ this.setLayout(this.layout);
+ }
+
+ setLayout(layout){
+
+ this.viewSettings.layout = layout;
+
+ this.mapping = new Mapping(layout.props, this.settings.direction, this.settings.axis);
+
+ if(this.views) {
+
+ this.views.forEach(function(view){
+ if (view) {
+ view.setLayout(layout);
+ }
+ });
+
+ }
+
+ }
+
+ updateWritingMode(mode) {
+ this.writingMode = mode;
+ }
+
+ updateAxis(axis, forceUpdate){
+
+ if (!forceUpdate && axis === this.settings.axis) {
+ return;
+ }
+
+ this.settings.axis = axis;
+
+ this.stage && this.stage.axis(axis);
+
+ this.viewSettings.axis = axis;
+
+ if (this.mapping) {
+ this.mapping = new Mapping(this.layout.props, this.settings.direction, this.settings.axis);
+ }
+
+ if (this.layout) {
+ if (axis === "vertical") {
+ this.layout.spread("none");
+ } else {
+ this.layout.spread(this.layout.settings.spread);
+ }
+ }
+ }
+
+ updateFlow(flow, defaultScrolledOverflow="auto"){
+ let isPaginated = (flow === "paginated" || flow === "auto");
+
+ this.isPaginated = isPaginated;
+
+ if (flow === "scrolled-doc" ||
+ flow === "scrolled-continuous" ||
+ flow === "scrolled") {
+ this.updateAxis("vertical");
+ } else {
+ this.updateAxis("horizontal");
+ }
+
+ this.viewSettings.flow = flow;
+
+ if (!this.settings.overflow) {
+ this.overflow = isPaginated ? "hidden" : defaultScrolledOverflow;
+ } else {
+ this.overflow = this.settings.overflow;
+ }
+
+ this.stage && this.stage.overflow(this.overflow);
+
+ this.updateLayout();
+
+ }
+
+ getContents(){
+ var contents = [];
+ if (!this.views) {
+ return contents;
+ }
+ this.views.forEach(function(view){
+ const viewContents = view && view.contents;
+ if (viewContents) {
+ contents.push(viewContents);
+ }
+ });
+ 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
+EventEmitter(DefaultViewManager.prototype);
+
+export default DefaultViewManager;
diff --git a/lib/epub.js/src/managers/helpers/snap.js b/lib/epub.js/src/managers/helpers/snap.js
new file mode 100644
index 0000000..db0aaff
--- /dev/null
+++ b/lib/epub.js/src/managers/helpers/snap.js
@@ -0,0 +1,338 @@
+import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
+import { EVENTS, DOM_EVENTS } from "../../utils/constants";
+import EventEmitter from "event-emitter";
+
+// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
+const PI_D2 = (Math.PI / 2);
+const EASING_EQUATIONS = {
+ easeOutSine: function (pos) {
+ return Math.sin(pos * PI_D2);
+ },
+ easeInOutSine: function (pos) {
+ return (-0.5 * (Math.cos(Math.PI * pos) - 1));
+ },
+ easeInOutQuint: function (pos) {
+ if ((pos /= 0.5) < 1) {
+ return 0.5 * Math.pow(pos, 5);
+ }
+ return 0.5 * (Math.pow((pos - 2), 5) + 2);
+ },
+ easeInCubic: function(pos) {
+ return Math.pow(pos, 3);
+ }
+};
+
+class Snap {
+ constructor(manager, options) {
+
+ this.settings = extend({
+ duration: 80,
+ minVelocity: 0.2,
+ minDistance: 10,
+ easing: EASING_EQUATIONS['easeInCubic']
+ }, options || {});
+
+ this.supportsTouch = this.supportsTouch();
+
+ if (this.supportsTouch) {
+ this.setup(manager);
+ }
+ }
+
+ setup(manager) {
+ this.manager = manager;
+
+ this.layout = this.manager.layout;
+
+ this.fullsize = this.manager.settings.fullsize;
+ if (this.fullsize) {
+ this.element = this.manager.stage.element;
+ this.scroller = window;
+ this.disableScroll();
+ } else {
+ this.element = this.manager.stage.container;
+ this.scroller = this.element;
+ this.element.style["WebkitOverflowScrolling"] = "touch";
+ }
+
+ // this.overflow = this.manager.overflow;
+
+ // set lookahead offset to page width
+ this.manager.settings.offset = this.layout.width;
+ this.manager.settings.afterScrolledTimeout = this.settings.duration * 2;
+
+ this.isVertical = this.manager.settings.axis === "vertical";
+
+ // disable snapping if not paginated or axis in not horizontal
+ if (!this.manager.isPaginated || this.isVertical) {
+ return;
+ }
+
+ this.touchCanceler = false;
+ this.resizeCanceler = false;
+ this.snapping = false;
+
+
+ this.scrollLeft;
+ this.scrollTop;
+
+ this.startTouchX = undefined;
+ this.startTouchY = undefined;
+ this.startTime = undefined;
+ this.endTouchX = undefined;
+ this.endTouchY = undefined;
+ this.endTime = undefined;
+
+ this.addListeners();
+ }
+
+ supportsTouch() {
+ if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
+ return true;
+ }
+
+ return false;
+ }
+
+ disableScroll() {
+ this.element.style.overflow = "hidden";
+ }
+
+ enableScroll() {
+ this.element.style.overflow = "";
+ }
+
+ addListeners() {
+ this._onResize = this.onResize.bind(this);
+ window.addEventListener('resize', this._onResize);
+
+ this._onScroll = this.onScroll.bind(this);
+ this.scroller.addEventListener('scroll', this._onScroll);
+
+ this._onTouchStart = this.onTouchStart.bind(this);
+ this.scroller.addEventListener('touchstart', this._onTouchStart, { passive: true });
+ this.on('touchstart', this._onTouchStart);
+
+ this._onTouchMove = this.onTouchMove.bind(this);
+ this.scroller.addEventListener('touchmove', this._onTouchMove, { passive: true });
+ this.on('touchmove', this._onTouchMove);
+
+ this._onTouchEnd = this.onTouchEnd.bind(this);
+ this.scroller.addEventListener('touchend', this._onTouchEnd, { passive: true });
+ this.on('touchend', this._onTouchEnd);
+
+ this._afterDisplayed = this.afterDisplayed.bind(this);
+ this.manager.on(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
+ }
+
+ removeListeners() {
+ window.removeEventListener('resize', this._onResize);
+ this._onResize = undefined;
+
+ this.scroller.removeEventListener('scroll', this._onScroll);
+ this._onScroll = undefined;
+
+ this.scroller.removeEventListener('touchstart', this._onTouchStart, { passive: true });
+ this.off('touchstart', this._onTouchStart);
+ this._onTouchStart = undefined;
+
+ this.scroller.removeEventListener('touchmove', this._onTouchMove, { passive: true });
+ this.off('touchmove', this._onTouchMove);
+ this._onTouchMove = undefined;
+
+ this.scroller.removeEventListener('touchend', this._onTouchEnd, { passive: true });
+ this.off('touchend', this._onTouchEnd);
+ this._onTouchEnd = undefined;
+
+ this.manager.off(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
+ this._afterDisplayed = undefined;
+ }
+
+ afterDisplayed(view) {
+ let contents = view.contents;
+ ["touchstart", "touchmove", "touchend"].forEach((e) => {
+ contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
+ });
+ }
+
+ triggerViewEvent(e, contents){
+ this.emit(e.type, e, contents);
+ }
+
+ onScroll(e) {
+ this.scrollLeft = this.fullsize ? window.scrollX : this.scroller.scrollLeft;
+ this.scrollTop = this.fullsize ? window.scrollY : this.scroller.scrollTop;
+ }
+
+ onResize(e) {
+ this.resizeCanceler = true;
+ }
+
+ onTouchStart(e) {
+ let { screenX, screenY } = e.touches[0];
+
+ if (this.fullsize) {
+ this.enableScroll();
+ }
+
+ this.touchCanceler = true;
+
+ if (!this.startTouchX) {
+ this.startTouchX = screenX;
+ this.startTouchY = screenY;
+ this.startTime = this.now();
+ }
+
+ this.endTouchX = screenX;
+ this.endTouchY = screenY;
+ this.endTime = this.now();
+ }
+
+ onTouchMove(e) {
+ let { screenX, screenY } = e.touches[0];
+ let deltaY = Math.abs(screenY - this.endTouchY);
+
+ this.touchCanceler = true;
+
+
+ if (!this.fullsize && deltaY < 10) {
+ this.element.scrollLeft -= screenX - this.endTouchX;
+ }
+
+ this.endTouchX = screenX;
+ this.endTouchY = screenY;
+ this.endTime = this.now();
+ }
+
+ onTouchEnd(e) {
+ if (this.fullsize) {
+ this.disableScroll();
+ }
+
+ this.touchCanceler = false;
+
+ let swipped = this.wasSwiped();
+
+ if (swipped !== 0) {
+ this.snap(swipped);
+ } else {
+ this.snap();
+ }
+
+ this.startTouchX = undefined;
+ this.startTouchY = undefined;
+ this.startTime = undefined;
+ this.endTouchX = undefined;
+ this.endTouchY = undefined;
+ this.endTime = undefined;
+ }
+
+ wasSwiped() {
+ let snapWidth = this.layout.pageWidth * this.layout.divisor;
+ let distance = (this.endTouchX - this.startTouchX);
+ let absolute = Math.abs(distance);
+ let time = this.endTime - this.startTime;
+ let velocity = (distance / time);
+ let minVelocity = this.settings.minVelocity;
+
+ if (absolute <= this.settings.minDistance || absolute >= snapWidth) {
+ return 0;
+ }
+
+ if (velocity > minVelocity) {
+ // previous
+ return -1;
+ } else if (velocity < -minVelocity) {
+ // next
+ return 1;
+ }
+ }
+
+ needsSnap() {
+ let left = this.scrollLeft;
+ let snapWidth = this.layout.pageWidth * this.layout.divisor;
+ return (left % snapWidth) !== 0;
+ }
+
+ snap(howMany=0) {
+ let left = this.scrollLeft;
+ let snapWidth = this.layout.pageWidth * this.layout.divisor;
+ let snapTo = Math.round(left / snapWidth) * snapWidth;
+
+ if (howMany) {
+ snapTo += (howMany * snapWidth);
+ }
+
+ return this.smoothScrollTo(snapTo);
+ }
+
+ smoothScrollTo(destination) {
+ const deferred = new defer();
+ const start = this.scrollLeft;
+ const startTime = this.now();
+
+ const duration = this.settings.duration;
+ const easing = this.settings.easing;
+
+ this.snapping = true;
+
+ // add animation loop
+ function tick() {
+ const now = this.now();
+ const time = Math.min(1, ((now - startTime) / duration));
+ const timeFunction = easing(time);
+
+
+ if (this.touchCanceler || this.resizeCanceler) {
+ this.resizeCanceler = false;
+ this.snapping = false;
+ deferred.resolve();
+ return;
+ }
+
+ if (time < 1) {
+ window.requestAnimationFrame(tick.bind(this));
+ this.scrollTo(start + ((destination - start) * time), 0);
+ } else {
+ this.scrollTo(destination, 0);
+ this.snapping = false;
+ deferred.resolve();
+ }
+ }
+
+ tick.call(this);
+
+ return deferred.promise;
+ }
+
+ scrollTo(left=0, top=0) {
+ if (this.fullsize) {
+ window.scroll(left, top);
+ } else {
+ this.scroller.scrollLeft = left;
+ this.scroller.scrollTop = top;
+ }
+ }
+
+ now() {
+ return ('now' in window.performance) ? performance.now() : new Date().getTime();
+ }
+
+ destroy() {
+ if (!this.scroller) {
+ return;
+ }
+
+ if (this.fullsize) {
+ this.enableScroll();
+ }
+
+ this.removeListeners();
+
+ this.scroller = undefined;
+ }
+}
+
+EventEmitter(Snap.prototype);
+
+export default Snap;
diff --git a/lib/epub.js/src/managers/helpers/stage.js b/lib/epub.js/src/managers/helpers/stage.js
new file mode 100644
index 0000000..d0f67e6
--- /dev/null
+++ b/lib/epub.js/src/managers/helpers/stage.js
@@ -0,0 +1,363 @@
+import {uuid, isNumber, isElement, windowBounds, extend} from "../../utils/core";
+import throttle from 'lodash/throttle'
+
+class Stage {
+ constructor(_options) {
+ this.settings = _options || {};
+ this.id = "epubjs-container-" + uuid();
+
+ this.container = this.create(this.settings);
+
+ if(this.settings.hidden) {
+ this.wrapper = this.wrap(this.container);
+ }
+
+ }
+
+ /*
+ * Creates an element to render to.
+ * Resizes to passed width and height or to the elements size
+ */
+ create(options){
+ 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;
+
+ extend(this.settings, options);
+
+ if(options.height && isNumber(options.height)) {
+ height = options.height + "px";
+ }
+
+ if(options.width && isNumber(options.width)) {
+ width = options.width + "px";
+ }
+
+ // Create new container element
+ let container = document.createElement("div");
+
+ container.id = this.id;
+ container.classList.add("epub-container");
+
+ // Style Element
+ // container.style.fontSize = "0";
+ container.style.wordSpacing = "0";
+ container.style.lineHeight = "0";
+ container.style.verticalAlign = "top";
+ container.style.position = "relative";
+
+ if(axis === "horizontal") {
+ // container.style.whiteSpace = "nowrap";
+ container.style.display = "flex";
+ container.style.flexDirection = "row";
+ container.style.flexWrap = "nowrap";
+ }
+
+ if(width){
+ container.style.width = width;
+ }
+
+ if(height){
+ container.style.height = height;
+ }
+
+ if (overflow) {
+ if (overflow === "scroll" && axis === "vertical") {
+ container.style["overflow-y"] = overflow;
+ container.style["overflow-x"] = "hidden";
+ } else if (overflow === "scroll" && axis === "horizontal") {
+ container.style["overflow-y"] = "hidden";
+ container.style["overflow-x"] = overflow;
+ } else {
+ 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;
+ }
+
+ wrap(container) {
+ var wrapper = document.createElement("div");
+
+ wrapper.style.visibility = "hidden";
+ wrapper.style.overflow = "hidden";
+ wrapper.style.width = "0";
+ wrapper.style.height = "0";
+
+ wrapper.appendChild(container);
+ return wrapper;
+ }
+
+
+ getElement(_element){
+ var element;
+
+ if(isElement(_element)) {
+ element = _element;
+ } else if (typeof _element === "string") {
+ element = document.getElementById(_element);
+ }
+
+ if(!element){
+ throw new Error("Not an Element");
+ }
+
+ return element;
+ }
+
+ attachTo(what){
+
+ var element = this.getElement(what);
+ var base;
+
+ if(!element){
+ return;
+ }
+
+ if(this.settings.hidden) {
+ base = this.wrapper;
+ } else {
+ base = this.container;
+ }
+
+ element.appendChild(base);
+
+ this.element = element;
+
+ return element;
+
+ }
+
+ getContainer() {
+ return this.container;
+ }
+
+ onResize(func){
+ // Only listen to window for resize event if width and height are not fixed.
+ // This applies if it is set to a percent or auto.
+ if(!isNumber(this.settings.width) ||
+ !isNumber(this.settings.height) ) {
+ this.resizeFunc = throttle(func, 50);
+ window.addEventListener("resize", this.resizeFunc, false);
+ }
+
+ }
+
+ onOrientationChange(func){
+ this.orientationChangeFunc = func;
+ window.addEventListener("orientationchange", this.orientationChangeFunc, false);
+ }
+
+ size(width, height){
+ var bounds;
+ let _width = width || this.settings.width;
+ let _height = height || this.settings.height;
+
+ // If width or height are set to false, inherit them from containing element
+ if(width === null) {
+ bounds = this.element.getBoundingClientRect();
+
+ if(bounds.width) {
+ width = Math.floor(bounds.width);
+ this.container.style.width = width + "px";
+ }
+ } else {
+ if (isNumber(width)) {
+ this.container.style.width = width + "px";
+ } else {
+ this.container.style.width = width;
+ }
+ }
+
+ if(height === null) {
+ bounds = bounds || this.element.getBoundingClientRect();
+
+ if(bounds.height) {
+ height = bounds.height;
+ this.container.style.height = height + "px";
+ }
+
+ } else {
+ if (isNumber(height)) {
+ this.container.style.height = height + "px";
+ } else {
+ this.container.style.height = height;
+ }
+ }
+
+ if(!isNumber(width)) {
+ width = this.container.clientWidth;
+ }
+
+ if(!isNumber(height)) {
+ height = this.container.clientHeight;
+ }
+
+ this.containerStyles = window.getComputedStyle(this.container);
+
+ this.containerPadding = {
+ left: parseFloat(this.containerStyles["padding-left"]) || 0,
+ right: parseFloat(this.containerStyles["padding-right"]) || 0,
+ top: parseFloat(this.containerStyles["padding-top"]) || 0,
+ bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
+ };
+
+ // Bounds not set, get them from window
+ let _windowBounds = windowBounds();
+ let bodyStyles = window.getComputedStyle(document.body);
+ let bodyPadding = {
+ left: parseFloat(bodyStyles["padding-left"]) || 0,
+ right: parseFloat(bodyStyles["padding-right"]) || 0,
+ top: parseFloat(bodyStyles["padding-top"]) || 0,
+ bottom: parseFloat(bodyStyles["padding-bottom"]) || 0
+ };
+
+ if (!_width) {
+ width = _windowBounds.width -
+ bodyPadding.left -
+ bodyPadding.right;
+ }
+
+ if ((this.settings.fullsize && !_height) || !_height) {
+ height = _windowBounds.height -
+ bodyPadding.top -
+ bodyPadding.bottom;
+ }
+
+ return {
+ width: width -
+ this.containerPadding.left -
+ this.containerPadding.right,
+ height: height -
+ this.containerPadding.top -
+ this.containerPadding.bottom
+ };
+
+ }
+
+ bounds(){
+ let box;
+ if (this.container.style.overflow !== "visible") {
+ box = this.container && this.container.getBoundingClientRect();
+ }
+
+ if(!box || !box.width || !box.height) {
+ return windowBounds();
+ } else {
+ return box;
+ }
+
+ }
+
+ getSheet(){
+ var style = document.createElement("style");
+
+ // WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
+ style.appendChild(document.createTextNode(""));
+
+ document.head.appendChild(style);
+
+ return style.sheet;
+ }
+
+ addStyleRules(selector, rulesArray){
+ var scope = "#" + this.id + " ";
+ var rules = "";
+
+ if(!this.sheet){
+ this.sheet = this.getSheet();
+ }
+
+ rulesArray.forEach(function(set) {
+ for (var prop in set) {
+ if(set.hasOwnProperty(prop)) {
+ rules += prop + ":" + set[prop] + ";";
+ }
+ }
+ });
+
+ this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
+ }
+
+ axis(axis) {
+ if(axis === "horizontal") {
+ this.container.style.display = "flex";
+ this.container.style.flexDirection = "row";
+ this.container.style.flexWrap = "nowrap";
+ } else {
+ this.container.style.display = "block";
+ }
+ this.settings.axis = axis;
+ }
+
+ // orientation(orientation) {
+ // if (orientation === "landscape") {
+ //
+ // } else {
+ //
+ // }
+ //
+ // 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;
+ }
+ this.settings.dir = dir;
+ }
+
+ overflow(overflow) {
+ if (this.container) {
+ if (overflow === "scroll" && this.settings.axis === "vertical") {
+ this.container.style["overflow-y"] = overflow;
+ this.container.style["overflow-x"] = "hidden";
+ } else if (overflow === "scroll" && this.settings.axis === "horizontal") {
+ this.container.style["overflow-y"] = "hidden";
+ this.container.style["overflow-x"] = overflow;
+ } else {
+ this.container.style["overflow"] = overflow;
+ }
+ }
+ this.settings.overflow = overflow;
+ }
+
+ destroy() {
+ var base;
+
+ if (this.element) {
+
+ if(this.settings.hidden) {
+ base = this.wrapper;
+ } else {
+ base = this.container;
+ }
+
+ if(this.element.contains(this.container)) {
+ this.element.removeChild(this.container);
+ }
+
+ window.removeEventListener("resize", this.resizeFunc);
+ window.removeEventListener("orientationChange", this.orientationChangeFunc);
+
+ }
+ }
+}
+
+export default Stage;
diff --git a/lib/epub.js/src/managers/helpers/views.js b/lib/epub.js/src/managers/helpers/views.js
new file mode 100644
index 0000000..4368da2
--- /dev/null
+++ b/lib/epub.js/src/managers/helpers/views.js
@@ -0,0 +1,167 @@
+class Views {
+ constructor(container) {
+ this.container = container;
+ this._views = [];
+ this.length = 0;
+ this.hidden = false;
+ }
+
+ all() {
+ return this._views;
+ }
+
+ first() {
+ return this._views[0];
+ }
+
+ last() {
+ return this._views[this._views.length-1];
+ }
+
+ indexOf(view) {
+ return this._views.indexOf(view);
+ }
+
+ slice() {
+ return this._views.slice.apply(this._views, arguments);
+ }
+
+ get(i) {
+ return this._views[i];
+ }
+
+ append(view){
+ this._views.push(view);
+ if(this.container){
+ this.container.appendChild(view.element);
+ }
+ this.length++;
+ return view;
+ }
+
+ prepend(view){
+ this._views.unshift(view);
+ if(this.container){
+ this.container.insertBefore(view.element, this.container.firstChild);
+ }
+ this.length++;
+ return view;
+ }
+
+ insert(view, index) {
+ this._views.splice(index, 0, view);
+
+ if(this.container){
+ if(index < this.container.children.length){
+ this.container.insertBefore(view.element, this.container.children[index]);
+ } else {
+ this.container.appendChild(view.element);
+ }
+ }
+
+ this.length++;
+ return view;
+ }
+
+ remove(view) {
+ var index = this._views.indexOf(view);
+
+ if(index > -1) {
+ this._views.splice(index, 1);
+ }
+
+
+ this.destroy(view);
+
+ this.length--;
+ }
+
+ destroy(view) {
+ if(view.displayed){
+ view.destroy();
+ }
+
+ if(this.container){
+ this.container.removeChild(view.element);
+ }
+ view = null;
+ }
+
+ // Iterators
+
+ forEach() {
+ return this._views.forEach.apply(this._views, arguments);
+ }
+
+ clear(){
+ // Remove all views
+ var view;
+ var len = this.length;
+
+ if(!this.length) return;
+
+ for (var i = 0; i < len; i++) {
+ view = this._views[i];
+ this.destroy(view);
+ }
+
+ this._views = [];
+ this.length = 0;
+ }
+
+ find(section){
+
+ var view;
+ var len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ view = this._views[i];
+ if(view.displayed && view.section.index == section.index) {
+ return view;
+ }
+ }
+
+ }
+
+ displayed(){
+ var displayed = [];
+ var view;
+ var len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ view = this._views[i];
+ if(view.displayed){
+ displayed.push(view);
+ }
+ }
+ return displayed;
+ }
+
+ show(){
+ var view;
+ var len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ view = this._views[i];
+ if(view.displayed){
+ view.show();
+ }
+ }
+ this.hidden = false;
+ }
+
+ hide(){
+ var view;
+ var len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ view = this._views[i];
+ if(view.displayed){
+ view.hide();
+ }
+ }
+ this.hidden = true;
+ }
+}
+
+export default Views;
diff --git a/lib/epub.js/src/managers/views/iframe.js b/lib/epub.js/src/managers/views/iframe.js
new file mode 100644
index 0000000..76b2c1d
--- /dev/null
+++ b/lib/epub.js/src/managers/views/iframe.js
@@ -0,0 +1,835 @@
+import EventEmitter from "event-emitter";
+import {extend, borders, uuid, isNumber, bounds, defer, createBlobUrl, revokeBlobUrl} from "../../utils/core";
+import EpubCFI from "../../epubcfi";
+import Contents from "../../contents";
+import { EVENTS } from "../../utils/constants";
+import { Pane, Highlight, Underline } from "marks-pane";
+
+class IframeView {
+ constructor(section, options) {
+ this.settings = extend({
+ ignoreClass : "",
+ axis: undefined, //options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
+ direction: undefined,
+ width: 0,
+ height: 0,
+ layout: undefined,
+ globalLayoutProperties: {},
+ method: undefined,
+ forceRight: false
+ }, options || {});
+
+ this.id = "epubjs-view-" + uuid();
+ this.section = section;
+ this.index = section.index;
+
+ this.element = this.container(this.settings.axis);
+
+ this.added = false;
+ this.displayed = false;
+ this.rendered = false;
+
+ // this.width = this.settings.width;
+ // this.height = this.settings.height;
+
+ this.fixedWidth = 0;
+ this.fixedHeight = 0;
+
+ // Blank Cfi for Parsing
+ this.epubcfi = new EpubCFI();
+
+ this.layout = this.settings.layout;
+ // Dom events to listen for
+ // this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
+
+ this.pane = undefined;
+ this.highlights = {};
+ this.underlines = {};
+ this.marks = {};
+
+ }
+
+ container(axis) {
+ var element = document.createElement("div");
+
+ element.classList.add("epub-view");
+
+ // this.element.style.minHeight = "100px";
+ element.style.height = "0px";
+ element.style.width = "0px";
+ element.style.overflow = "hidden";
+ element.style.position = "relative";
+ element.style.display = "block";
+
+ if(axis && axis == "horizontal"){
+ element.style.flex = "none";
+ } else {
+ element.style.flex = "initial";
+ }
+
+ return element;
+ }
+
+ create() {
+
+ if(this.iframe) {
+ return this.iframe;
+ }
+
+ if(!this.element) {
+ this.element = this.createContainer();
+ }
+
+ this.iframe = document.createElement("iframe");
+ this.iframe.id = this.id;
+ this.iframe.scrolling = "no"; // Might need to be removed: breaks ios width calculations
+ this.iframe.style.overflow = "hidden";
+ this.iframe.seamless = "seamless";
+ // Back up if seamless isn't supported
+ this.iframe.style.border = "none";
+
+ this.iframe.setAttribute("enable-annotation", "true");
+
+ this.resizing = true;
+
+ // this.iframe.style.display = "none";
+ this.element.style.visibility = "hidden";
+ this.iframe.style.visibility = "hidden";
+
+ this.iframe.style.width = "0";
+ this.iframe.style.height = "0";
+ this._width = 0;
+ this._height = 0;
+
+ this.element.setAttribute("ref", this.index);
+
+ this.added = true;
+
+ this.elementBounds = bounds(this.element);
+
+ // if(width || height){
+ // this.resize(width, height);
+ // } else if(this.width && this.height){
+ // this.resize(this.width, this.height);
+ // } else {
+ // this.iframeBounds = bounds(this.iframe);
+ // }
+
+
+ if(("srcdoc" in this.iframe)) {
+ this.supportsSrcdoc = true;
+ } else {
+ this.supportsSrcdoc = false;
+ }
+
+ if (!this.settings.method) {
+ this.settings.method = this.supportsSrcdoc ? "srcdoc" : "write";
+ }
+
+ return this.iframe;
+ }
+
+ render(request, show) {
+
+ // view.onLayout = this.layout.format.bind(this.layout);
+ this.create();
+
+ // Fit to size of the container, apply padding
+ this.size();
+
+ if(!this.sectionRender) {
+ this.sectionRender = this.section.render(request);
+ }
+
+ // Render Chain
+ return this.sectionRender
+ .then(function(contents){
+ return this.load(contents);
+ }.bind(this))
+ .then(function(){
+
+ // find and report the writingMode axis
+ let writingMode = this.contents.writingMode();
+
+ // Set the axis based on the flow and writing mode
+ let axis;
+ if (this.settings.flow === "scrolled") {
+ axis = (writingMode.indexOf("vertical") === 0) ? "horizontal" : "vertical";
+ } else {
+ axis = (writingMode.indexOf("vertical") === 0) ? "vertical" : "horizontal";
+ }
+
+ if (writingMode.indexOf("vertical") === 0 && this.settings.flow === "paginated") {
+ this.layout.delta = this.layout.height;
+ }
+
+ this.setAxis(axis);
+ this.emit(EVENTS.VIEWS.AXIS, axis);
+
+ this.setWritingMode(writingMode);
+ this.emit(EVENTS.VIEWS.WRITING_MODE, writingMode);
+
+
+ // apply the layout function to the contents
+ this.layout.format(this.contents, this.section, this.axis);
+
+ // Listen for events that require an expansion of the iframe
+ this.addListeners();
+
+ return new Promise((resolve, reject) => {
+ // Expand the iframe to the full size of the content
+ this.expand();
+
+ if (this.settings.forceRight) {
+ this.element.style.marginLeft = this.width() + "px";
+ }
+ resolve();
+ });
+
+ }.bind(this), function(e){
+ this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
+ return new Promise((resolve, reject) => {
+ reject(e);
+ });
+ }.bind(this))
+ .then(function() {
+ this.emit(EVENTS.VIEWS.RENDERED, this.section);
+ }.bind(this));
+
+ }
+
+ reset () {
+ if (this.iframe) {
+ this.iframe.style.width = "0";
+ this.iframe.style.height = "0";
+ this._width = 0;
+ this._height = 0;
+ this._textWidth = undefined;
+ this._contentWidth = undefined;
+ this._textHeight = undefined;
+ this._contentHeight = undefined;
+ }
+ this._needsReframe = true;
+ }
+
+ // Determine locks base on settings
+ size(_width, _height) {
+ var width = _width || this.settings.width;
+ var height = _height || this.settings.height;
+
+ if(this.layout.name === "pre-paginated") {
+ this.lock("both", width, height);
+ } else if(this.settings.axis === "horizontal") {
+ this.lock("height", width, height);
+ } else {
+ this.lock("width", width, height);
+ }
+
+ this.settings.width = width;
+ this.settings.height = height;
+ }
+
+ // Lock an axis to element dimensions, taking borders into account
+ lock(what, width, height) {
+ var elBorders = borders(this.element);
+ var iframeBorders;
+
+ if(this.iframe) {
+ iframeBorders = borders(this.iframe);
+ } else {
+ iframeBorders = {width: 0, height: 0};
+ }
+
+ if(what == "width" && isNumber(width)){
+ this.lockedWidth = width - elBorders.width - iframeBorders.width;
+ // this.resize(this.lockedWidth, width); // width keeps ratio correct
+ }
+
+ if(what == "height" && isNumber(height)){
+ this.lockedHeight = height - elBorders.height - iframeBorders.height;
+ // this.resize(width, this.lockedHeight);
+ }
+
+ if(what === "both" &&
+ isNumber(width) &&
+ isNumber(height)){
+
+ this.lockedWidth = width - elBorders.width - iframeBorders.width;
+ this.lockedHeight = height - elBorders.height - iframeBorders.height;
+ // this.resize(this.lockedWidth, this.lockedHeight);
+ }
+
+ if(this.displayed && this.iframe) {
+
+ // this.contents.layout();
+ this.expand();
+ }
+
+
+
+ }
+
+ // Resize a single axis based on content dimensions
+ expand(force) {
+ var width = this.lockedWidth;
+ var height = this.lockedHeight;
+ var columns;
+
+ var textWidth, textHeight;
+
+ if(!this.iframe || this._expanding) return;
+
+ this._expanding = true;
+
+ if(this.layout.name === "pre-paginated") {
+ width = this.layout.columnWidth;
+ height = this.layout.height;
+ }
+ // Expand Horizontally
+ else if(this.settings.axis === "horizontal") {
+ // Get the width of the text
+ width = this.contents.textWidth();
+
+ if (width % this.layout.pageWidth > 0) {
+ width = Math.ceil(width / this.layout.pageWidth) * this.layout.pageWidth;
+ }
+
+ if (this.settings.forceEvenPages) {
+ columns = (width / this.layout.pageWidth);
+ if ( this.layout.divisor > 1 &&
+ this.layout.name === "reflowable" &&
+ (columns % 2 > 0)) {
+ // add a blank page
+ width += this.layout.pageWidth;
+ }
+ }
+
+ } // Expand Vertically
+ else if(this.settings.axis === "vertical") {
+ height = this.contents.textHeight();
+ if (this.settings.flow === "paginated" &&
+ height % this.layout.height > 0) {
+ height = Math.ceil(height / this.layout.height) * this.layout.height;
+ }
+ }
+
+ // Only Resize if dimensions have changed or
+ // if Frame is still hidden, so needs reframing
+ if(this._needsReframe || width != this._width || height != this._height){
+ this.reframe(width, height);
+ }
+
+ this._expanding = false;
+ }
+
+ reframe(width, height) {
+ var size;
+
+ if(isNumber(width)){
+ this.element.style.width = width + "px";
+ this.iframe.style.width = width + "px";
+ this._width = width;
+ }
+
+ if(isNumber(height)){
+ this.element.style.height = height + "px";
+ this.iframe.style.height = height + "px";
+ this._height = height;
+ }
+
+ let widthDelta = this.prevBounds ? width - this.prevBounds.width : width;
+ let heightDelta = this.prevBounds ? height - this.prevBounds.height : height;
+
+ size = {
+ width: width,
+ height: height,
+ widthDelta: widthDelta,
+ heightDelta: heightDelta,
+ };
+
+ this.pane && this.pane.render();
+
+ requestAnimationFrame(() => {
+ let mark;
+ for (let m in this.marks) {
+ if (this.marks.hasOwnProperty(m)) {
+ mark = this.marks[m];
+ this.placeMark(mark.element, mark.range);
+ }
+ }
+ });
+
+ this.onResize(this, size);
+
+ this.emit(EVENTS.VIEWS.RESIZED, size);
+
+ this.prevBounds = size;
+
+ this.elementBounds = bounds(this.element);
+
+ }
+
+
+ load(contents) {
+ var loading = new defer();
+ var loaded = loading.promise;
+
+ if(!this.iframe) {
+ loading.reject(new Error("No Iframe Available"));
+ return loaded;
+ }
+
+ this.iframe.onload = function(event) {
+
+ this.onLoad(event, loading);
+
+ }.bind(this);
+
+ if (this.settings.method === "blobUrl") {
+ this.blobUrl = createBlobUrl(contents, "application/xhtml+xml");
+ this.iframe.src = this.blobUrl;
+ this.element.appendChild(this.iframe);
+ } else if(this.settings.method === "srcdoc"){
+ this.iframe.srcdoc = contents;
+ this.element.appendChild(this.iframe);
+ } else {
+
+ this.element.appendChild(this.iframe);
+
+ this.document = this.iframe.contentDocument;
+
+ if(!this.document) {
+ loading.reject(new Error("No Document Available"));
+ return loaded;
+ }
+
+ this.iframe.contentDocument.open();
+ // For Cordova windows platform
+ if(window.MSApp && MSApp.execUnsafeLocalFunction) {
+ var outerThis = this;
+ MSApp.execUnsafeLocalFunction(function () {
+ outerThis.iframe.contentDocument.write(contents);
+ });
+ } else {
+ this.iframe.contentDocument.write(contents);
+ }
+ this.iframe.contentDocument.close();
+
+ }
+
+ return loaded;
+ }
+
+ onLoad(event, promise) {
+
+ this.window = this.iframe.contentWindow;
+ this.document = this.iframe.contentDocument;
+
+ this.contents = new Contents(this.document, this.document.body, this.section.cfiBase, this.section.index);
+
+ this.rendering = false;
+
+ var link = this.document.querySelector("link[rel='canonical']");
+ if (link) {
+ link.setAttribute("href", this.section.canonical);
+ } else {
+ link = this.document.createElement("link");
+ link.setAttribute("rel", "canonical");
+ link.setAttribute("href", this.section.canonical);
+ this.document.querySelector("head").appendChild(link);
+ }
+
+ this.contents.on(EVENTS.CONTENTS.EXPAND, () => {
+ if(this.displayed && this.iframe) {
+ this.expand();
+ if (this.contents) {
+ this.layout.format(this.contents);
+ }
+ }
+ });
+
+ this.contents.on(EVENTS.CONTENTS.RESIZE, (e) => {
+ if(this.displayed && this.iframe) {
+ this.expand();
+ if (this.contents) {
+ this.layout.format(this.contents);
+ }
+ }
+ });
+
+ promise.resolve(this.contents);
+ }
+
+ setLayout(layout) {
+ this.layout = layout;
+
+ if (this.contents) {
+ this.layout.format(this.contents);
+ this.expand();
+ }
+ }
+
+ setAxis(axis) {
+
+ this.settings.axis = axis;
+
+ if(axis == "horizontal"){
+ this.element.style.flex = "none";
+ } else {
+ this.element.style.flex = "initial";
+ }
+
+ this.size();
+
+ }
+
+ setWritingMode(mode) {
+ // this.element.style.writingMode = writingMode;
+ this.writingMode = mode;
+ }
+
+ addListeners() {
+ //TODO: Add content listeners for expanding
+ }
+
+ removeListeners(layoutFunc) {
+ //TODO: remove content listeners for expanding
+ }
+
+ display(request) {
+ var displayed = new defer();
+
+ if (!this.displayed) {
+
+ this.render(request)
+ .then(function () {
+
+ this.emit(EVENTS.VIEWS.DISPLAYED, this);
+ this.onDisplayed(this);
+
+ this.displayed = true;
+ displayed.resolve(this);
+
+ }.bind(this), function (err) {
+ displayed.reject(err, this);
+ });
+
+ } else {
+ displayed.resolve(this);
+ }
+
+
+ return displayed.promise;
+ }
+
+ show() {
+
+ this.element.style.visibility = "visible";
+
+ if(this.iframe){
+ this.iframe.style.visibility = "visible";
+
+ // Remind Safari to redraw the iframe
+ this.iframe.style.transform = "translateZ(0)";
+ this.iframe.offsetWidth;
+ this.iframe.style.transform = null;
+ }
+
+ this.emit(EVENTS.VIEWS.SHOWN, this);
+ }
+
+ hide() {
+ // this.iframe.style.display = "none";
+ this.element.style.visibility = "hidden";
+ this.iframe.style.visibility = "hidden";
+
+ this.stopExpanding = true;
+ this.emit(EVENTS.VIEWS.HIDDEN, this);
+ }
+
+ offset() {
+ return {
+ top: this.element.offsetTop,
+ left: this.element.offsetLeft
+ }
+ }
+
+ width() {
+ return this._width;
+ }
+
+ height() {
+ return this._height;
+ }
+
+ position() {
+ return this.element.getBoundingClientRect();
+ }
+
+ locationOf(target) {
+ var parentPos = this.iframe.getBoundingClientRect();
+ var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
+
+ return {
+ "left": targetPos.left,
+ "top": targetPos.top
+ };
+ }
+
+ onDisplayed(view) {
+ // Stub, override with a custom functions
+ }
+
+ onResize(view, e) {
+ // Stub, override with a custom functions
+ }
+
+ bounds(force) {
+ if(force || !this.elementBounds) {
+ this.elementBounds = bounds(this.element);
+ }
+
+ return this.elementBounds;
+ }
+
+ highlight(cfiRange, data={}, cb, className = "epubjs-hl", styles = {}) {
+ if (!this.contents) {
+ return;
+ }
+ const attributes = Object.assign({"fill": "yellow", "fill-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
+ let range = this.contents.range(cfiRange);
+
+ let emitter = () => {
+ this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
+ };
+
+ data["epubcfi"] = cfiRange;
+
+ if (!this.pane) {
+ this.pane = new Pane(this.iframe, this.element);
+ }
+
+ let m = new Highlight(range, className, data, attributes);
+ let h = this.pane.addMark(m);
+
+ this.highlights[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
+
+ h.element.setAttribute("ref", className);
+ h.element.addEventListener("click", emitter);
+ h.element.addEventListener("touchstart", emitter);
+
+ if (cb) {
+ h.element.addEventListener("click", cb);
+ h.element.addEventListener("touchstart", cb);
+ }
+ return h;
+ }
+
+ underline(cfiRange, data={}, cb, className = "epubjs-ul", styles = {}) {
+ if (!this.contents) {
+ return;
+ }
+ const attributes = Object.assign({"stroke": "black", "stroke-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
+ let range = this.contents.range(cfiRange);
+ let emitter = () => {
+ this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
+ };
+
+ data["epubcfi"] = cfiRange;
+
+ if (!this.pane) {
+ this.pane = new Pane(this.iframe, this.element);
+ }
+
+ let m = new Underline(range, className, data, attributes);
+ let h = this.pane.addMark(m);
+
+ this.underlines[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
+
+ h.element.setAttribute("ref", className);
+ h.element.addEventListener("click", emitter);
+ h.element.addEventListener("touchstart", emitter);
+
+ if (cb) {
+ h.element.addEventListener("click", cb);
+ h.element.addEventListener("touchstart", cb);
+ }
+ return h;
+ }
+
+ mark(cfiRange, data={}, cb) {
+ if (!this.contents) {
+ return;
+ }
+
+ if (cfiRange in this.marks) {
+ let item = this.marks[cfiRange];
+ return item;
+ }
+
+ let range = this.contents.range(cfiRange);
+ if (!range) {
+ return;
+ }
+ let container = range.commonAncestorContainer;
+ let parent = (container.nodeType === 1) ? container : container.parentNode;
+
+ let emitter = (e) => {
+ this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
+ };
+
+ if (range.collapsed && container.nodeType === 1) {
+ range = new Range();
+ range.selectNodeContents(container);
+ } else if (range.collapsed) { // Webkit doesn't like collapsed ranges
+ range = new Range();
+ range.selectNodeContents(parent);
+ }
+
+ let mark = this.document.createElement("a");
+ mark.setAttribute("ref", "epubjs-mk");
+ mark.style.position = "absolute";
+
+ mark.dataset["epubcfi"] = cfiRange;
+
+ if (data) {
+ Object.keys(data).forEach((key) => {
+ mark.dataset[key] = data[key];
+ });
+ }
+
+ if (cb) {
+ mark.addEventListener("click", cb);
+ mark.addEventListener("touchstart", cb);
+ }
+
+ mark.addEventListener("click", emitter);
+ mark.addEventListener("touchstart", emitter);
+
+ this.placeMark(mark, range);
+
+ this.element.appendChild(mark);
+
+ this.marks[cfiRange] = { "element": mark, "range": range, "listeners": [emitter, cb] };
+
+ return parent;
+ }
+
+ placeMark(element, range) {
+ let top, right, left;
+
+ if(this.layout.name === "pre-paginated" ||
+ this.settings.axis !== "horizontal") {
+ let pos = range.getBoundingClientRect();
+ top = pos.top;
+ right = pos.right;
+ } else {
+ // Element might break columns, so find the left most element
+ let rects = range.getClientRects();
+
+ let rect;
+ for (var i = 0; i != rects.length; i++) {
+ rect = rects[i];
+ if (!left || rect.left < left) {
+ left = rect.left;
+ // right = rect.right;
+ right = Math.ceil(left / this.layout.props.pageWidth) * this.layout.props.pageWidth - (this.layout.gap / 2);
+ top = rect.top;
+ }
+ }
+ }
+
+ element.style.top = `${top}px`;
+ element.style.left = `${right}px`;
+ }
+
+ unhighlight(cfiRange) {
+ let item;
+ if (cfiRange in this.highlights) {
+ item = this.highlights[cfiRange];
+
+ this.pane.removeMark(item.mark);
+ item.listeners.forEach((l) => {
+ if (l) {
+ item.element.removeEventListener("click", l);
+ item.element.removeEventListener("touchstart", l);
+ };
+ });
+ delete this.highlights[cfiRange];
+ }
+ }
+
+ ununderline(cfiRange) {
+ let item;
+ if (cfiRange in this.underlines) {
+ item = this.underlines[cfiRange];
+ this.pane.removeMark(item.mark);
+ item.listeners.forEach((l) => {
+ if (l) {
+ item.element.removeEventListener("click", l);
+ item.element.removeEventListener("touchstart", l);
+ };
+ });
+ delete this.underlines[cfiRange];
+ }
+ }
+
+ unmark(cfiRange) {
+ let item;
+ if (cfiRange in this.marks) {
+ item = this.marks[cfiRange];
+ this.element.removeChild(item.element);
+ item.listeners.forEach((l) => {
+ if (l) {
+ item.element.removeEventListener("click", l);
+ item.element.removeEventListener("touchstart", l);
+ };
+ });
+ delete this.marks[cfiRange];
+ }
+ }
+
+ destroy() {
+
+ for (let cfiRange in this.highlights) {
+ this.unhighlight(cfiRange);
+ }
+
+ for (let cfiRange in this.underlines) {
+ this.ununderline(cfiRange);
+ }
+
+ for (let cfiRange in this.marks) {
+ this.unmark(cfiRange);
+ }
+
+ if (this.blobUrl) {
+ revokeBlobUrl(this.blobUrl);
+ }
+
+ if(this.displayed){
+ this.displayed = false;
+
+ this.removeListeners();
+ this.contents.destroy();
+
+ this.stopExpanding = true;
+ this.element.removeChild(this.iframe);
+
+ this.iframe = undefined;
+ this.contents = undefined;
+
+ this._textWidth = null;
+ this._textHeight = null;
+ this._width = null;
+ this._height = null;
+ }
+
+ // this.element.style.height = "0px";
+ // this.element.style.width = "0px";
+ }
+}
+
+EventEmitter(IframeView.prototype);
+
+export default IframeView;
diff --git a/lib/epub.js/src/managers/views/inline.js b/lib/epub.js/src/managers/views/inline.js
new file mode 100644
index 0000000..072b586
--- /dev/null
+++ b/lib/epub.js/src/managers/views/inline.js
@@ -0,0 +1,432 @@
+import EventEmitter from "event-emitter";
+import {extend, borders, uuid, isNumber, bounds, defer, qs, parse} from "../../utils/core";
+import EpubCFI from "../../epubcfi";
+import Contents from "../../contents";
+import { EVENTS } from "../../utils/constants";
+
+class InlineView {
+ constructor(section, options) {
+ this.settings = extend({
+ ignoreClass : "",
+ axis: "vertical",
+ width: 0,
+ height: 0,
+ layout: undefined,
+ globalLayoutProperties: {},
+ }, options || {});
+
+ this.id = "epubjs-view:" + uuid();
+ this.section = section;
+ this.index = section.index;
+
+ this.element = this.container(this.settings.axis);
+
+ this.added = false;
+ this.displayed = false;
+ this.rendered = false;
+
+ this.width = this.settings.width;
+ this.height = this.settings.height;
+
+ this.fixedWidth = 0;
+ this.fixedHeight = 0;
+
+ // Blank Cfi for Parsing
+ this.epubcfi = new EpubCFI();
+
+ this.layout = this.settings.layout;
+ // Dom events to listen for
+ // this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
+
+ }
+
+ container(axis) {
+ var element = document.createElement("div");
+
+ element.classList.add("epub-view");
+
+ // if(this.settings.axis === "horizontal") {
+ // element.style.width = "auto";
+ // element.style.height = "0";
+ // } else {
+ // element.style.width = "0";
+ // element.style.height = "auto";
+ // }
+
+ element.style.overflow = "hidden";
+
+ if(axis && axis == "horizontal"){
+ element.style.display = "inline-block";
+ } else {
+ element.style.display = "block";
+ }
+
+ return element;
+ }
+
+ create() {
+
+ if(this.frame) {
+ return this.frame;
+ }
+
+ if(!this.element) {
+ this.element = this.createContainer();
+ }
+
+ this.frame = document.createElement("div");
+ this.frame.id = this.id;
+ this.frame.style.overflow = "hidden";
+ this.frame.style.wordSpacing = "initial";
+ this.frame.style.lineHeight = "initial";
+
+ this.resizing = true;
+
+ // this.frame.style.display = "none";
+ this.element.style.visibility = "hidden";
+ this.frame.style.visibility = "hidden";
+
+ if(this.settings.axis === "horizontal") {
+ this.frame.style.width = "auto";
+ this.frame.style.height = "0";
+ } else {
+ this.frame.style.width = "0";
+ this.frame.style.height = "auto";
+ }
+
+ this._width = 0;
+ this._height = 0;
+
+ this.element.appendChild(this.frame);
+ this.added = true;
+
+ this.elementBounds = bounds(this.element);
+
+ return this.frame;
+ }
+
+ render(request, show) {
+
+ // view.onLayout = this.layout.format.bind(this.layout);
+ this.create();
+
+ // Fit to size of the container, apply padding
+ this.size();
+
+ // Render Chain
+ return this.section.render(request)
+ .then(function(contents){
+ return this.load(contents);
+ }.bind(this))
+ // .then(function(doc){
+ // return this.hooks.content.trigger(view, this);
+ // }.bind(this))
+ .then(function(){
+ // this.settings.layout.format(view.contents);
+ // return this.hooks.layout.trigger(view, this);
+ }.bind(this))
+ // .then(function(){
+ // return this.display();
+ // }.bind(this))
+ // .then(function(){
+ // return this.hooks.render.trigger(view, this);
+ // }.bind(this))
+ .then(function(){
+
+ // apply the layout function to the contents
+ this.settings.layout.format(this.contents);
+
+ // Expand the iframe to the full size of the content
+ // this.expand();
+
+ // Listen for events that require an expansion of the iframe
+ this.addListeners();
+
+ if(show !== false) {
+ //this.q.enqueue(function(view){
+ this.show();
+ //}, view);
+ }
+ // this.map = new Map(view, this.layout);
+ //this.hooks.show.trigger(view, this);
+ this.emit(EVENTS.VIEWS.RENDERED, this.section);
+
+ }.bind(this))
+ .catch(function(e){
+ this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
+ }.bind(this));
+
+ }
+
+ // Determine locks base on settings
+ size(_width, _height) {
+ var width = _width || this.settings.width;
+ var height = _height || this.settings.height;
+
+ if(this.layout.name === "pre-paginated") {
+ // TODO: check if these are different than the size set in chapter
+ this.lock("both", width, height);
+ } else if(this.settings.axis === "horizontal") {
+ this.lock("height", width, height);
+ } else {
+ this.lock("width", width, height);
+ }
+
+ }
+
+ // Lock an axis to element dimensions, taking borders into account
+ lock(what, width, height) {
+ var elBorders = borders(this.element);
+ var iframeBorders;
+
+ if(this.frame) {
+ iframeBorders = borders(this.frame);
+ } else {
+ iframeBorders = {width: 0, height: 0};
+ }
+
+ if(what == "width" && isNumber(width)){
+ this.lockedWidth = width - elBorders.width - iframeBorders.width;
+ this.resize(this.lockedWidth, false); // width keeps ratio correct
+ }
+
+ if(what == "height" && isNumber(height)){
+ this.lockedHeight = height - elBorders.height - iframeBorders.height;
+ this.resize(false, this.lockedHeight);
+ }
+
+ if(what === "both" &&
+ isNumber(width) &&
+ isNumber(height)){
+
+ this.lockedWidth = width - elBorders.width - iframeBorders.width;
+ this.lockedHeight = height - elBorders.height - iframeBorders.height;
+
+ this.resize(this.lockedWidth, this.lockedHeight);
+ }
+
+ }
+
+ // Resize a single axis based on content dimensions
+ expand(force) {
+ var width = this.lockedWidth;
+ var height = this.lockedHeight;
+
+ var textWidth, textHeight;
+
+ if(!this.frame || this._expanding) return;
+
+ this._expanding = true;
+
+ // Expand Horizontally
+ if(this.settings.axis === "horizontal") {
+ width = this.contentWidth(textWidth);
+ } // Expand Vertically
+ else if(this.settings.axis === "vertical") {
+ height = this.contentHeight(textHeight);
+ }
+
+ // Only Resize if dimensions have changed or
+ // if Frame is still hidden, so needs reframing
+ if(this._needsReframe || width != this._width || height != this._height){
+ this.resize(width, height);
+ }
+
+ this._expanding = false;
+ }
+
+ contentWidth(min) {
+ return this.frame.scrollWidth;
+ }
+
+ contentHeight(min) {
+ return this.frame.scrollHeight;
+ }
+
+
+ resize(width, height) {
+
+ if(!this.frame) return;
+
+ if(isNumber(width)){
+ this.frame.style.width = width + "px";
+ this._width = width;
+ }
+
+ if(isNumber(height)){
+ this.frame.style.height = height + "px";
+ this._height = height;
+ }
+
+ this.prevBounds = this.elementBounds;
+
+ this.elementBounds = bounds(this.element);
+
+ let size = {
+ width: this.elementBounds.width,
+ height: this.elementBounds.height,
+ widthDelta: this.elementBounds.width - this.prevBounds.width,
+ heightDelta: this.elementBounds.height - this.prevBounds.height,
+ };
+
+ this.onResize(this, size);
+
+ this.emit(EVENTS.VIEWS.RESIZED, size);
+
+ }
+
+
+ load(contents) {
+ var loading = new defer();
+ var loaded = loading.promise;
+ var doc = parse(contents, "text/html");
+ var body = qs(doc, "body");
+
+ /*
+ var srcs = doc.querySelectorAll("[src]");
+
+ Array.prototype.slice.call(srcs)
+ .forEach(function(item) {
+ var src = item.getAttribute("src");
+ var assetUri = URI(src);
+ var origin = assetUri.origin();
+ var absoluteUri;
+
+ if (!origin) {
+ absoluteUri = assetUri.absoluteTo(this.section.url);
+ item.src = absoluteUri;
+ }
+ }.bind(this));
+ */
+ this.frame.innerHTML = body.innerHTML;
+
+ this.document = this.frame.ownerDocument;
+ this.window = this.document.defaultView;
+
+ this.contents = new Contents(this.document, this.frame);
+
+ this.rendering = false;
+
+ loading.resolve(this.contents);
+
+
+ return loaded;
+ }
+
+ setLayout(layout) {
+ this.layout = layout;
+ }
+
+
+ resizeListenters() {
+ // Test size again
+ // clearTimeout(this.expanding);
+ // this.expanding = setTimeout(this.expand.bind(this), 350);
+ }
+
+ addListeners() {
+ //TODO: Add content listeners for expanding
+ }
+
+ removeListeners(layoutFunc) {
+ //TODO: remove content listeners for expanding
+ }
+
+ display(request) {
+ var displayed = new defer();
+
+ if (!this.displayed) {
+
+ this.render(request).then(function () {
+
+ this.emit(EVENTS.VIEWS.DISPLAYED, this);
+ this.onDisplayed(this);
+
+ this.displayed = true;
+
+ displayed.resolve(this);
+
+ }.bind(this));
+
+ } else {
+ displayed.resolve(this);
+ }
+
+
+ return displayed.promise;
+ }
+
+ show() {
+
+ this.element.style.visibility = "visible";
+
+ if(this.frame){
+ this.frame.style.visibility = "visible";
+ }
+
+ this.emit(EVENTS.VIEWS.SHOWN, this);
+ }
+
+ hide() {
+ // this.frame.style.display = "none";
+ this.element.style.visibility = "hidden";
+ this.frame.style.visibility = "hidden";
+
+ this.stopExpanding = true;
+ this.emit(EVENTS.VIEWS.HIDDEN, this);
+ }
+
+ position() {
+ return this.element.getBoundingClientRect();
+ }
+
+ locationOf(target) {
+ var parentPos = this.frame.getBoundingClientRect();
+ var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
+
+ return {
+ "left": window.scrollX + parentPos.left + targetPos.left,
+ "top": window.scrollY + parentPos.top + targetPos.top
+ };
+ }
+
+ onDisplayed(view) {
+ // Stub, override with a custom functions
+ }
+
+ onResize(view, e) {
+ // Stub, override with a custom functions
+ }
+
+ bounds() {
+ if(!this.elementBounds) {
+ this.elementBounds = bounds(this.element);
+ }
+ return this.elementBounds;
+ }
+
+ destroy() {
+
+ if(this.displayed){
+ this.displayed = false;
+
+ this.removeListeners();
+
+ this.stopExpanding = true;
+ this.element.removeChild(this.frame);
+ this.displayed = false;
+ this.frame = null;
+
+ this._textWidth = null;
+ this._textHeight = null;
+ this._width = null;
+ this._height = null;
+ }
+ // this.element.style.height = "0px";
+ // this.element.style.width = "0px";
+ }
+}
+
+EventEmitter(InlineView.prototype);
+
+export default InlineView;