epubjs
Version:
Parse and Render Epubs
845 lines (680 loc) • 21.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _eventEmitter = require("event-emitter");
var _eventEmitter2 = _interopRequireDefault(_eventEmitter);
var _core = require("../../utils/core");
var _epubcfi = require("../../epubcfi");
var _epubcfi2 = _interopRequireDefault(_epubcfi);
var _contents = require("../../contents");
var _contents2 = _interopRequireDefault(_contents);
var _constants = require("../../utils/constants");
var _marksPane = require("marks-pane");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var IframeView = function () {
function IframeView(section, options) {
_classCallCheck(this, IframeView);
this.settings = (0, _core.extend)({
ignoreClass: "",
axis: options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
direction: undefined,
width: 0,
height: 0,
layout: undefined,
globalLayoutProperties: {},
method: undefined
}, options || {});
this.id = "epubjs-view-" + (0, _core.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 _epubcfi2.default();
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 = {};
}
_createClass(IframeView, [{
key: "container",
value: function 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;
}
}, {
key: "create",
value: function 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.element.appendChild(this.iframe);
this.added = true;
this.elementBounds = (0, _core.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;
}
}, {
key: "render",
value: function 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 () {
var _this = this;
// apply the layout function to the contents
this.layout.format(this.contents);
// find and report the writingMode axis
var writingMode = this.contents.writingMode();
var axis = writingMode.indexOf("vertical") === 0 ? "vertical" : "horizontal";
this.setAxis(axis);
this.emit(_constants.EVENTS.VIEWS.AXIS, axis);
// Listen for events that require an expansion of the iframe
this.addListeners();
return new Promise(function (resolve, reject) {
// Expand the iframe to the full size of the content
_this.expand();
resolve();
});
}.bind(this), function (e) {
this.emit(_constants.EVENTS.VIEWS.LOAD_ERROR, e);
return new Promise(function (resolve, reject) {
reject(e);
});
}.bind(this)).then(function () {
this.emit(_constants.EVENTS.VIEWS.RENDERED, this.section);
}.bind(this));
}
}, {
key: "reset",
value: function 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
}, {
key: "size",
value: function 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
}, {
key: "lock",
value: function lock(what, width, height) {
var elBorders = (0, _core.borders)(this.element);
var iframeBorders;
if (this.iframe) {
iframeBorders = (0, _core.borders)(this.iframe);
} else {
iframeBorders = { width: 0, height: 0 };
}
if (what == "width" && (0, _core.isNumber)(width)) {
this.lockedWidth = width - elBorders.width - iframeBorders.width;
// this.resize(this.lockedWidth, width); // width keeps ratio correct
}
if (what == "height" && (0, _core.isNumber)(height)) {
this.lockedHeight = height - elBorders.height - iframeBorders.height;
// this.resize(width, this.lockedHeight);
}
if (what === "both" && (0, _core.isNumber)(width) && (0, _core.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
}, {
key: "expand",
value: function 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.delta;
if (this.layout.divisor > 1 && this.layout.name === "reflowable" && columns % 2 > 0) {
// add a blank page
width += this.layout.gap + this.layout.columnWidth;
}
}
} // Expand Vertically
else if (this.settings.axis === "vertical") {
height = this.contents.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.reframe(width, height);
}
this._expanding = false;
}
}, {
key: "reframe",
value: function reframe(width, height) {
var size;
if ((0, _core.isNumber)(width)) {
this.element.style.width = width + "px";
this.iframe.style.width = width + "px";
this._width = width;
}
if ((0, _core.isNumber)(height)) {
this.element.style.height = height + "px";
this.iframe.style.height = height + "px";
this._height = height;
}
var widthDelta = this.prevBounds ? width - this.prevBounds.width : width;
var heightDelta = this.prevBounds ? height - this.prevBounds.height : height;
size = {
width: width,
height: height,
widthDelta: widthDelta,
heightDelta: heightDelta
};
this.pane && this.pane.render();
this.onResize(this, size);
this.emit(_constants.EVENTS.VIEWS.RESIZED, size);
this.prevBounds = size;
this.elementBounds = (0, _core.bounds)(this.element);
}
}, {
key: "load",
value: function load(contents) {
var loading = new _core.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 = (0, _core.createBlobUrl)(contents, "application/xhtml+xml");
this.iframe.src = this.blobUrl;
} else if (this.settings.method === "srcdoc") {
this.iframe.srcdoc = contents;
} else {
this.document = this.iframe.contentDocument;
if (!this.document) {
loading.reject(new Error("No Document Available"));
return loaded;
}
this.iframe.contentDocument.open();
this.iframe.contentDocument.write(contents);
this.iframe.contentDocument.close();
}
return loaded;
}
}, {
key: "onLoad",
value: function onLoad(event, promise) {
var _this2 = this;
this.window = this.iframe.contentWindow;
this.document = this.iframe.contentDocument;
this.contents = new _contents2.default(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(_constants.EVENTS.CONTENTS.EXPAND, function () {
if (_this2.displayed && _this2.iframe) {
_this2.expand();
if (_this2.contents) {
_this2.layout.format(_this2.contents);
}
}
});
this.contents.on(_constants.EVENTS.CONTENTS.RESIZE, function (e) {
if (_this2.displayed && _this2.iframe) {
_this2.expand();
if (_this2.contents) {
_this2.layout.format(_this2.contents);
}
}
});
promise.resolve(this.contents);
}
}, {
key: "setLayout",
value: function setLayout(layout) {
this.layout = layout;
if (this.contents) {
this.layout.format(this.contents);
this.expand();
}
}
}, {
key: "setAxis",
value: function setAxis(axis) {
// Force vertical for scrolled
if (this.layout.props.flow === "scrolled") {
axis = "vertical";
}
this.settings.axis = axis;
if (axis == "horizontal") {
this.element.style.flex = "none";
} else {
this.element.style.flex = "initial";
}
this.size();
}
}, {
key: "addListeners",
value: function addListeners() {
//TODO: Add content listeners for expanding
}
}, {
key: "removeListeners",
value: function removeListeners(layoutFunc) {
//TODO: remove content listeners for expanding
}
}, {
key: "display",
value: function display(request) {
var displayed = new _core.defer();
if (!this.displayed) {
this.render(request).then(function () {
this.emit(_constants.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;
}
}, {
key: "show",
value: function show() {
this.element.style.visibility = "visible";
if (this.iframe) {
this.iframe.style.visibility = "visible";
}
this.emit(_constants.EVENTS.VIEWS.SHOWN, this);
}
}, {
key: "hide",
value: function hide() {
// this.iframe.style.display = "none";
this.element.style.visibility = "hidden";
this.iframe.style.visibility = "hidden";
this.stopExpanding = true;
this.emit(_constants.EVENTS.VIEWS.HIDDEN, this);
}
}, {
key: "offset",
value: function offset() {
return {
top: this.element.offsetTop,
left: this.element.offsetLeft
};
}
}, {
key: "width",
value: function width() {
return this._width;
}
}, {
key: "height",
value: function height() {
return this._height;
}
}, {
key: "position",
value: function position() {
return this.element.getBoundingClientRect();
}
}, {
key: "locationOf",
value: function locationOf(target) {
var parentPos = this.iframe.getBoundingClientRect();
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
return {
"left": targetPos.left,
"top": targetPos.top
};
}
}, {
key: "onDisplayed",
value: function onDisplayed(view) {
// Stub, override with a custom functions
}
}, {
key: "onResize",
value: function onResize(view, e) {
// Stub, override with a custom functions
}
}, {
key: "bounds",
value: function bounds(force) {
if (force || !this.elementBounds) {
this.elementBounds = (0, _core.bounds)(this.element);
}
return this.elementBounds;
}
}, {
key: "highlight",
value: function highlight(cfiRange) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var cb = arguments[2];
var _this3 = this;
var className = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "epubjs-hl";
var styles = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
if (!this.contents) {
return;
}
var attributes = Object.assign({ "fill": "yellow", "fill-opacity": "0.3", "mix-blend-mode": "multiply" }, styles);
var range = this.contents.range(cfiRange);
var emitter = function emitter() {
_this3.emit(_constants.EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
};
data["epubcfi"] = cfiRange;
if (!this.pane) {
this.pane = new _marksPane.Pane(this.iframe, this.element);
}
var m = new _marksPane.Highlight(range, className, data, attributes);
var 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;
}
}, {
key: "underline",
value: function underline(cfiRange) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var cb = arguments[2];
var _this4 = this;
var className = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "epubjs-ul";
var styles = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
if (!this.contents) {
return;
}
var attributes = Object.assign({ "stroke": "black", "stroke-opacity": "0.3", "mix-blend-mode": "multiply" }, styles);
var range = this.contents.range(cfiRange);
var emitter = function emitter() {
_this4.emit(_constants.EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
};
data["epubcfi"] = cfiRange;
if (!this.pane) {
this.pane = new _marksPane.Pane(this.iframe, this.element);
}
var m = new _marksPane.Underline(range, className, data, attributes);
var 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;
}
}, {
key: "mark",
value: function mark(cfiRange) {
var _this5 = this;
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var cb = arguments[2];
if (!this.contents) {
return;
}
if (cfiRange in this.marks) {
var item = this.marks[cfiRange];
return item;
}
var range = this.contents.range(cfiRange);
if (!range) {
return;
}
var container = range.commonAncestorContainer;
var parent = container.nodeType === 1 ? container : container.parentNode;
var emitter = function emitter(e) {
_this5.emit(_constants.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);
}
var top = void 0,
right = void 0,
left = void 0;
if (this.layout.name === "pre-paginated" || this.settings.axis !== "horizontal") {
var pos = range.getBoundingClientRect();
top = pos.top;
right = pos.right;
} else {
// Element might break columns, so find the left most element
var rects = range.getClientRects();
var rect = void 0;
for (var i = 0; i != rects.length; i++) {
rect = rects[i];
if (!left || rect.left < left) {
left = rect.left;
right = left + this.layout.columnWidth - this.layout.gap;
top = rect.top;
}
}
}
var mark = this.document.createElement("a");
mark.setAttribute("ref", "epubjs-mk");
mark.style.position = "absolute";
mark.style.top = top + "px";
mark.style.left = right + "px";
mark.dataset["epubcfi"] = cfiRange;
if (data) {
Object.keys(data).forEach(function (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.element.appendChild(mark);
this.marks[cfiRange] = { "element": mark, "listeners": [emitter, cb] };
return parent;
}
}, {
key: "unhighlight",
value: function unhighlight(cfiRange) {
var item = void 0;
if (cfiRange in this.highlights) {
item = this.highlights[cfiRange];
this.pane.removeMark(item.mark);
item.listeners.forEach(function (l) {
if (l) {
item.element.removeEventListener("click", l);
};
});
delete this.highlights[cfiRange];
}
}
}, {
key: "ununderline",
value: function ununderline(cfiRange) {
var item = void 0;
if (cfiRange in this.underlines) {
item = this.underlines[cfiRange];
this.pane.removeMark(item.mark);
item.listeners.forEach(function (l) {
if (l) {
item.element.removeEventListener("click", l);
};
});
delete this.underlines[cfiRange];
}
}
}, {
key: "unmark",
value: function unmark(cfiRange) {
var item = void 0;
if (cfiRange in this.marks) {
item = this.marks[cfiRange];
this.element.removeChild(item.element);
item.listeners.forEach(function (l) {
if (l) {
item.element.removeEventListener("click", l);
};
});
delete this.marks[cfiRange];
}
}
}, {
key: "destroy",
value: function destroy() {
for (var cfiRange in this.highlights) {
this.unhighlight(cfiRange);
}
for (var _cfiRange in this.underlines) {
this.ununderline(_cfiRange);
}
for (var _cfiRange2 in this.marks) {
this.unmark(_cfiRange2);
}
if (this.blobUrl) {
(0, _core.revokeBlobUrl)(this.blobUrl);
}
if (this.displayed) {
this.displayed = false;
this.removeListeners();
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";
}
}]);
return IframeView;
}();
(0, _eventEmitter2.default)(IframeView.prototype);
exports.default = IframeView;
module.exports = exports["default"];