epubjs
Version:
Parse and Render Epubs
791 lines (655 loc) • 19.5 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 _mapping = require("./mapping");
var _mapping2 = _interopRequireDefault(_mapping);
var _replacements = require("./utils/replacements");
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"); } }
// Dom events to listen for
var EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
var Contents = function () {
function Contents(doc, content, cfiBase) {
_classCallCheck(this, Contents);
// Blank Cfi for Parsing
this.epubcfi = new _epubcfi2.default();
this.document = doc;
this.documentElement = this.document.documentElement;
this.content = content || this.document.body;
this.window = this.document.defaultView;
this._size = {
width: 0,
height: 0
};
this.cfiBase = cfiBase || "";
this.listeners();
}
_createClass(Contents, [{
key: "width",
value: function width(w) {
// var frame = this.documentElement;
var frame = this.content;
if (w && (0, _core.isNumber)(w)) {
w = w + "px";
}
if (w) {
frame.style.width = w;
// this.content.style.width = w;
}
return this.window.getComputedStyle(frame)["width"];
}
}, {
key: "height",
value: function height(h) {
// var frame = this.documentElement;
var frame = this.content;
if (h && (0, _core.isNumber)(h)) {
h = h + "px";
}
if (h) {
frame.style.height = h;
// this.content.style.height = h;
}
return this.window.getComputedStyle(frame)["height"];
}
}, {
key: "contentWidth",
value: function contentWidth(w) {
var content = this.content || this.document.body;
if (w && (0, _core.isNumber)(w)) {
w = w + "px";
}
if (w) {
content.style.width = w;
}
return this.window.getComputedStyle(content)["width"];
}
}, {
key: "contentHeight",
value: function contentHeight(h) {
var content = this.content || this.document.body;
if (h && (0, _core.isNumber)(h)) {
h = h + "px";
}
if (h) {
content.style.height = h;
}
return this.window.getComputedStyle(content)["height"];
}
}, {
key: "textWidth",
value: function textWidth() {
var width;
var range = this.document.createRange();
var content = this.content || this.document.body;
// Select the contents of frame
range.selectNodeContents(content);
// get the width of the text content
width = range.getBoundingClientRect().width;
return width;
}
}, {
key: "textHeight",
value: function textHeight() {
var height;
var range = this.document.createRange();
var content = this.content || this.document.body;
range.selectNodeContents(content);
height = range.getBoundingClientRect().height;
return height;
}
}, {
key: "scrollWidth",
value: function scrollWidth() {
var width = this.documentElement.scrollWidth;
return width;
}
}, {
key: "scrollHeight",
value: function scrollHeight() {
var height = this.documentElement.scrollHeight;
return height;
}
}, {
key: "overflow",
value: function overflow(_overflow) {
if (_overflow) {
this.documentElement.style.overflow = _overflow;
}
return this.window.getComputedStyle(this.documentElement)["overflow"];
}
}, {
key: "overflowX",
value: function overflowX(overflow) {
if (overflow) {
this.documentElement.style.overflowX = overflow;
}
return this.window.getComputedStyle(this.documentElement)["overflowX"];
}
}, {
key: "overflowY",
value: function overflowY(overflow) {
if (overflow) {
this.documentElement.style.overflowY = overflow;
}
return this.window.getComputedStyle(this.documentElement)["overflowY"];
}
}, {
key: "css",
value: function css(property, value) {
var content = this.content || this.document.body;
if (value) {
content.style[property] = value;
}
return this.window.getComputedStyle(content)[property];
}
}, {
key: "viewport",
value: function viewport(options) {
var width, height, scale, scalable;
var $viewport = this.document.querySelector("meta[name='viewport']");
var newContent = "";
/*
* check for the viewport size
* <meta name="viewport" content="width=1024,height=697" />
*/
if ($viewport && $viewport.hasAttribute("content")) {
var content = $viewport.getAttribute("content");
var contents = content.split(/\s*,\s*/);
if (contents[0]) {
width = contents[0].replace("width=", "").trim();
}
if (contents[1]) {
height = contents[1].replace("height=", "").trim();
}
if (contents[2]) {
scale = contents[2].replace("initial-scale=", "").trim();
}
if (contents[3]) {
scalable = contents[3].replace("user-scalable=", "").trim();
}
}
if (options) {
newContent += "width=" + (options.width || width);
newContent += ", height=" + (options.height || height);
if (options.scale || scale) {
newContent += ", initial-scale=" + (options.scale || scale);
}
if (options.scalable || scalable) {
newContent += ", user-scalable=" + (options.scalable || scalable);
}
if (!$viewport) {
$viewport = this.document.createElement("meta");
$viewport.setAttribute("name", "viewport");
this.document.querySelector("head").appendChild($viewport);
}
$viewport.setAttribute("content", newContent);
}
return {
width: parseInt(width),
height: parseInt(height)
};
}
// layout(layoutFunc) {
//
// this.iframe.style.display = "inline-block";
//
// // Reset Body Styles
// this.content.style.margin = "0";
// //this.document.body.style.display = "inline-block";
// //this.document.documentElement.style.width = "auto";
//
// if(layoutFunc){
// layoutFunc(this);
// }
//
// this.onLayout(this);
//
// };
//
// onLayout(view) {
// // stub
// };
}, {
key: "expand",
value: function expand() {
this.emit("expand");
}
}, {
key: "listeners",
value: function listeners() {
this.imageLoadListeners();
this.mediaQueryListeners();
// this.fontLoadListeners();
this.addEventListeners();
this.addSelectionListeners();
this.resizeListeners();
this.linksHandler();
}
}, {
key: "removeListeners",
value: function removeListeners() {
this.removeEventListeners();
this.removeSelectionListeners();
}
}, {
key: "resizeListeners",
value: function resizeListeners() {
var width, height;
// Test size again
clearTimeout(this.expanding);
width = this.scrollWidth();
height = this.scrollHeight();
if (width != this._size.width || height != this._size.height) {
this._size = {
width: width,
height: height
};
this.emit("resize", this._size);
}
this.expanding = setTimeout(this.resizeListeners.bind(this), 350);
}
//https://github.com/tylergaw/media-query-events/blob/master/js/mq-events.js
}, {
key: "mediaQueryListeners",
value: function mediaQueryListeners() {
var sheets = this.document.styleSheets;
var mediaChangeHandler = function (m) {
if (m.matches && !this._expanding) {
setTimeout(this.expand.bind(this), 1);
// this.expand();
}
}.bind(this);
for (var i = 0; i < sheets.length; i += 1) {
var rules;
// Firefox errors if we access cssRules cross-domain
try {
rules = sheets[i].cssRules;
} catch (e) {
return;
}
if (!rules) return; // Stylesheets changed
for (var j = 0; j < rules.length; j += 1) {
//if (rules[j].constructor === CSSMediaRule) {
if (rules[j].media) {
var mql = this.window.matchMedia(rules[j].media.mediaText);
mql.addListener(mediaChangeHandler);
//mql.onchange = mediaChangeHandler;
}
}
}
}
}, {
key: "observe",
value: function observe(target) {
var renderer = this;
// create an observer instance
var observer = new MutationObserver(function (mutations) {
if (renderer._expanding) {
renderer.expand();
}
// mutations.forEach(function(mutation) {
// console.log(mutation);
// });
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true, subtree: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
return observer;
}
}, {
key: "imageLoadListeners",
value: function imageLoadListeners(target) {
var images = this.document.querySelectorAll("img");
var img;
for (var i = 0; i < images.length; i++) {
img = images[i];
if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
img.onload = this.expand.bind(this);
}
}
}
}, {
key: "fontLoadListeners",
value: function fontLoadListeners(target) {
if (!this.document || !this.document.fonts) {
return;
}
this.document.fonts.ready.then(function () {
this.expand();
}.bind(this));
}
}, {
key: "root",
value: function root() {
if (!this.document) return null;
return this.document.documentElement;
}
}, {
key: "locationOf",
value: function locationOf(target, ignoreClass) {
var position;
var targetPos = { "left": 0, "top": 0 };
if (!this.document) return;
if (this.epubcfi.isCfiString(target)) {
var range = new _epubcfi2.default(target).toRange(this.document, ignoreClass);
if (range) {
if (range.startContainer.nodeType === Node.ELEMENT_NODE) {
position = range.startContainer.getBoundingClientRect();
targetPos.left = position.left;
targetPos.top = position.top;
} else {
// Webkit does not handle collapsed range bounds correctly
// https://bugs.webkit.org/show_bug.cgi?id=138949
if (range.collapsed) {
position = range.getClientRects()[0];
} else {
position = range.getBoundingClientRect();
}
}
}
} else if (typeof target === "string" && target.indexOf("#") > -1) {
var id = target.substring(target.indexOf("#") + 1);
var el = this.document.getElementById(id);
if (el) {
position = el.getBoundingClientRect();
}
}
if (position) {
targetPos.left = position.left;
targetPos.top = position.top;
}
return targetPos;
}
}, {
key: "addStylesheet",
value: function addStylesheet(src) {
return new Promise(function (resolve, reject) {
var $stylesheet;
var ready = false;
if (!this.document) {
resolve(false);
return;
}
// Check if link already exists
$stylesheet = this.document.querySelector("link[href='" + src + "']");
if ($stylesheet) {
resolve(true);
return; // already present
}
$stylesheet = this.document.createElement("link");
$stylesheet.type = "text/css";
$stylesheet.rel = "stylesheet";
$stylesheet.href = src;
$stylesheet.onload = $stylesheet.onreadystatechange = function () {
if (!ready && (!this.readyState || this.readyState == "complete")) {
ready = true;
// Let apply
setTimeout(function () {
resolve(true);
}, 1);
}
};
this.document.head.appendChild($stylesheet);
}.bind(this));
}
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
}, {
key: "addStylesheetRules",
value: function addStylesheetRules(rules) {
var styleEl;
var styleSheet;
var key = "epubjs-inserted-css";
if (!this.document) return;
// Check if link already exists
styleEl = this.document.getElementById("#" + key);
if (!styleEl) {
styleEl = this.document.createElement("style");
styleEl.id = key;
}
// Append style element to head
this.document.head.appendChild(styleEl);
// Grab style sheet
styleSheet = styleEl.sheet;
for (var i = 0, rl = rules.length; i < rl; i++) {
var j = 1,
rule = rules[i],
selector = rules[i][0],
propStr = "";
// If the second argument of a rule is an array of arrays, correct our variables.
if (Object.prototype.toString.call(rule[1][0]) === "[object Array]") {
rule = rule[1];
j = 0;
}
for (var pl = rule.length; j < pl; j++) {
var prop = rule[j];
propStr += prop[0] + ":" + prop[1] + (prop[2] ? " !important" : "") + ";\n";
}
// Insert CSS Rule
styleSheet.insertRule(selector + "{" + propStr + "}", styleSheet.cssRules.length);
}
}
}, {
key: "addScript",
value: function addScript(src) {
return new Promise(function (resolve, reject) {
var $script;
var ready = false;
if (!this.document) {
resolve(false);
return;
}
$script = this.document.createElement("script");
$script.type = "text/javascript";
$script.async = true;
$script.src = src;
$script.onload = $script.onreadystatechange = function () {
if (!ready && (!this.readyState || this.readyState == "complete")) {
ready = true;
setTimeout(function () {
resolve(true);
}, 1);
}
};
this.document.head.appendChild($script);
}.bind(this));
}
}, {
key: "addClass",
value: function addClass(className) {
var content;
if (!this.document) return;
content = this.content || this.document.body;
content.classList.add(className);
}
}, {
key: "removeClass",
value: function removeClass(className) {
var content;
if (!this.document) return;
content = this.content || this.document.body;
content.classList.remove(className);
}
}, {
key: "addEventListeners",
value: function addEventListeners() {
if (!this.document) {
return;
}
EVENTS.forEach(function (eventName) {
this.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
}, this);
}
}, {
key: "removeEventListeners",
value: function removeEventListeners() {
if (!this.document) {
return;
}
EVENTS.forEach(function (eventName) {
this.document.removeEventListener(eventName, this.triggerEvent, false);
}, this);
}
// Pass browser events
}, {
key: "triggerEvent",
value: function triggerEvent(e) {
this.emit(e.type, e);
}
}, {
key: "addSelectionListeners",
value: function addSelectionListeners() {
if (!this.document) {
return;
}
this.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
}
}, {
key: "removeSelectionListeners",
value: function removeSelectionListeners() {
if (!this.document) {
return;
}
this.document.removeEventListener("selectionchange", this.onSelectionChange, false);
}
}, {
key: "onSelectionChange",
value: function onSelectionChange(e) {
if (this.selectionEndTimeout) {
clearTimeout(this.selectionEndTimeout);
}
this.selectionEndTimeout = setTimeout(function () {
var selection = this.window.getSelection();
this.triggerSelectedEvent(selection);
}.bind(this), 500);
}
}, {
key: "triggerSelectedEvent",
value: function triggerSelectedEvent(selection) {
var range, cfirange;
if (selection && selection.rangeCount > 0) {
range = selection.getRangeAt(0);
if (!range.collapsed) {
// cfirange = this.section.cfiFromRange(range);
cfirange = new _epubcfi2.default(range, this.cfiBase).toString();
this.emit("selected", cfirange);
this.emit("selectedRange", range);
}
}
}
}, {
key: "range",
value: function range(_cfi, ignoreClass) {
var cfi = new _epubcfi2.default(_cfi);
return cfi.toRange(this.document, ignoreClass);
}
}, {
key: "map",
value: function map(layout) {
var map = new _mapping2.default(layout);
return map.section();
}
}, {
key: "size",
value: function size(width, height) {
if (width >= 0) {
this.width(width);
}
if (height >= 0) {
this.height(height);
}
this.css("margin", "0");
this.css("boxSizing", "border-box");
}
}, {
key: "columns",
value: function columns(width, height, columnWidth, gap) {
var COLUMN_AXIS = (0, _core.prefixed)("columnAxis");
var COLUMN_GAP = (0, _core.prefixed)("columnGap");
var COLUMN_WIDTH = (0, _core.prefixed)("columnWidth");
var COLUMN_FILL = (0, _core.prefixed)("columnFill");
this.width(width);
this.height(height);
// Deal with Mobile trying to scale to viewport
this.viewport({ width: width, height: height, scale: 1.0 });
// this.overflowY("hidden");
this.css("overflowY", "hidden");
this.css("margin", "0");
this.css("boxSizing", "border-box");
this.css("maxWidth", "inherit");
this.css(COLUMN_AXIS, "horizontal");
this.css(COLUMN_FILL, "auto");
this.css(COLUMN_GAP, gap + "px");
this.css(COLUMN_WIDTH, columnWidth + "px");
}
}, {
key: "scaler",
value: function scaler(scale, offsetX, offsetY) {
var scaleStr = "scale(" + scale + ")";
var translateStr = "";
// this.css("position", "absolute"));
this.css("transformOrigin", "top left");
if (offsetX >= 0 || offsetY >= 0) {
translateStr = " translate(" + (offsetX || 0) + "px, " + (offsetY || 0) + "px )";
}
this.css("transform", scaleStr + translateStr);
}
}, {
key: "fit",
value: function fit(width, height) {
var viewport = this.viewport();
var widthScale = width / viewport.width;
var heightScale = height / viewport.height;
var scale = widthScale < heightScale ? widthScale : heightScale;
var offsetY = (height - viewport.height * scale) / 2;
this.width(width);
this.height(height);
this.overflow("hidden");
// Deal with Mobile trying to scale to viewport
this.viewport({ scale: 1.0 });
// Scale to the correct size
this.scaler(scale, 0, offsetY);
this.css("backgroundColor", "transparent");
}
}, {
key: "mapPage",
value: function mapPage(cfiBase, start, end) {
var mapping = new _mapping2.default();
return mapping.page(this, cfiBase, start, end);
}
}, {
key: "linksHandler",
value: function linksHandler() {
var _this = this;
(0, _replacements.replaceLinks)(this.content, function (href) {
_this.emit("link", href);
});
}
}, {
key: "destroy",
value: function destroy() {
// Stop observing
if (this.observer) {
this.observer.disconnect();
}
this.removeListeners();
}
}], [{
key: "listenedEvents",
get: function get() {
return EVENTS;
}
}]);
return Contents;
}();
(0, _eventEmitter2.default)(Contents.prototype);
exports.default = Contents;
module.exports = exports["default"];