vue-book-reader
Version:
<div align="center"> <img width=250 src="https://raw.githubusercontent.com/jinhuan138/vue--book-reader/master/public/logo.png" /> <h1>VueReader</h1> </div>
314 lines (313 loc) • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const parseViewport = (str) => {
var _a, _b;
return (_b = (_a = str == null ? void 0 : str.split(/[,;\s]/)) == null ? void 0 : _a.filter((x) => x)) == null ? void 0 : _b.map((x) => x.split("=").map((x2) => x2.trim()));
};
const getViewport = (doc, viewport) => {
var _a, _b;
if (doc.documentElement.localName === "svg") {
const [, , width, height] = ((_a = doc.documentElement.getAttribute("viewBox")) == null ? void 0 : _a.split(/\s/)) ?? [];
return { width, height };
}
const meta = parseViewport((_b = doc.querySelector('meta[name="viewport"]')) == null ? void 0 : _b.getAttribute("content"));
if (meta)
return Object.fromEntries(meta);
if (typeof viewport === "string")
return parseViewport(viewport);
if ((viewport == null ? void 0 : viewport.width) && viewport.height)
return viewport;
const img = doc.querySelector("img");
if (img)
return { width: img.naturalWidth, height: img.naturalHeight };
console.warn(new Error("Missing viewport properties"));
return { width: 1e3, height: 2e3 };
};
class FixedLayout extends HTMLElement {
static observedAttributes = ["zoom"];
#root = this.attachShadow({ mode: "closed" });
#observer = new ResizeObserver(() => this.#render());
#spreads;
#index = -1;
defaultViewport;
spread;
#portrait = false;
#left;
#right;
#center;
#side;
#zoom;
constructor() {
super();
const sheet = new CSSStyleSheet();
this.#root.adoptedStyleSheets = [sheet];
sheet.replaceSync(`:host {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
}`);
this.#observer.observe(this);
}
attributeChangedCallback(name, _, value) {
switch (name) {
case "zoom":
this.#zoom = value !== "fit-width" && value !== "fit-page" ? parseFloat(value) : value;
this.#render();
break;
}
}
async #createFrame({ index, src: srcOption }) {
const srcOptionIsString = typeof srcOption === "string";
const src = srcOptionIsString ? srcOption : srcOption == null ? void 0 : srcOption.src;
const onZoom = srcOptionIsString ? null : srcOption == null ? void 0 : srcOption.onZoom;
const element = document.createElement("div");
element.setAttribute("dir", "ltr");
const iframe = document.createElement("iframe");
element.append(iframe);
Object.assign(iframe.style, {
border: "0",
display: "none",
overflow: "hidden"
});
iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
iframe.setAttribute("scrolling", "no");
iframe.setAttribute("part", "filter");
this.#root.append(element);
if (!src)
return { blank: true, element, iframe };
return new Promise((resolve) => {
iframe.addEventListener("load", () => {
const doc = iframe.contentDocument;
this.dispatchEvent(new CustomEvent("load", { detail: { doc, index } }));
const { width, height } = getViewport(doc, this.defaultViewport);
resolve({
element,
iframe,
width: parseFloat(width),
height: parseFloat(height),
onZoom
});
}, { once: true });
iframe.src = src;
});
}
#render(side = this.#side) {
if (!side)
return;
const left = this.#left ?? {};
const right = this.#center ?? this.#right ?? {};
const target = side === "left" ? left : right;
const { width, height } = this.getBoundingClientRect();
const portrait = this.spread !== "both" && this.spread !== "portrait" && height > width;
this.#portrait = portrait;
const blankWidth = left.width ?? right.width ?? 0;
const blankHeight = left.height ?? right.height ?? 0;
const scale = typeof this.#zoom === "number" && !isNaN(this.#zoom) ? this.#zoom : (this.#zoom === "fit-width" ? portrait || this.#center ? width / (target.width ?? blankWidth) : width / ((left.width ?? blankWidth) + (right.width ?? blankWidth)) : portrait || this.#center ? Math.min(
width / (target.width ?? blankWidth),
height / (target.height ?? blankHeight)
) : Math.min(
width / ((left.width ?? blankWidth) + (right.width ?? blankWidth)),
height / Math.max(
left.height ?? blankHeight,
right.height ?? blankHeight
)
)) || 1;
const transform = (frame) => {
let { element, iframe, width: width2, height: height2, blank, onZoom } = frame;
if (!iframe)
return;
if (onZoom)
onZoom({ doc: frame.iframe.contentDocument, scale });
const iframeScale = onZoom ? scale : 1;
Object.assign(iframe.style, {
width: `${width2 * iframeScale}px`,
height: `${height2 * iframeScale}px`,
transform: onZoom ? "none" : `scale(${scale})`,
transformOrigin: "top left",
display: blank ? "none" : "block"
});
Object.assign(element.style, {
width: `${(width2 ?? blankWidth) * scale}px`,
height: `${(height2 ?? blankHeight) * scale}px`,
overflow: "hidden",
display: "block",
flexShrink: "0",
marginBlock: "auto"
});
if (portrait && frame !== target) {
element.style.display = "none";
}
};
if (this.#center) {
transform(this.#center);
} else {
transform(left);
transform(right);
}
}
async #showSpread({ left, right, center, side }) {
this.#root.replaceChildren();
this.#left = null;
this.#right = null;
this.#center = null;
if (center) {
this.#center = await this.#createFrame(center);
this.#side = "center";
this.#render();
} else {
this.#left = await this.#createFrame(left);
this.#right = await this.#createFrame(right);
this.#side = this.#left.blank ? "right" : this.#right.blank ? "left" : side;
this.#render();
}
}
#goLeft() {
var _a, _b, _c, _d;
if (this.#center || ((_a = this.#left) == null ? void 0 : _a.blank))
return;
if (this.#portrait && ((_d = (_c = (_b = this.#left) == null ? void 0 : _b.element) == null ? void 0 : _c.style) == null ? void 0 : _d.display) === "none") {
this.#side = "left";
this.#render();
this.#reportLocation("page");
return true;
}
}
#goRight() {
var _a, _b, _c, _d;
if (this.#center || ((_a = this.#right) == null ? void 0 : _a.blank))
return;
if (this.#portrait && ((_d = (_c = (_b = this.#right) == null ? void 0 : _b.element) == null ? void 0 : _c.style) == null ? void 0 : _d.display) === "none") {
this.#side = "right";
this.#render();
this.#reportLocation("page");
return true;
}
}
open(book) {
this.book = book;
const { rendition } = book;
this.spread = rendition == null ? void 0 : rendition.spread;
this.defaultViewport = rendition == null ? void 0 : rendition.viewport;
const rtl = book.dir === "rtl";
const ltr = !rtl;
this.rtl = rtl;
if ((rendition == null ? void 0 : rendition.spread) === "none")
this.#spreads = book.sections.map((section) => ({ center: section }));
else
this.#spreads = book.sections.reduce((arr, section, i) => {
const last = arr[arr.length - 1];
const { pageSpread } = section;
const newSpread = () => {
const spread = {};
arr.push(spread);
return spread;
};
if (pageSpread === "center") {
const spread = last.left || last.right ? newSpread() : last;
spread.center = section;
} else if (pageSpread === "left") {
const spread = last.center || last.left || ltr && i ? newSpread() : last;
spread.left = section;
} else if (pageSpread === "right") {
const spread = last.center || last.right || rtl && i ? newSpread() : last;
spread.right = section;
} else if (ltr) {
if (last.center || last.right)
newSpread().left = section;
else if (last.left || !i)
last.right = section;
else
last.left = section;
} else {
if (last.center || last.left)
newSpread().right = section;
else if (last.right || !i)
last.left = section;
else
last.right = section;
}
return arr;
}, [{}]);
}
get index() {
const spread = this.#spreads[this.#index];
const section = (spread == null ? void 0 : spread.center) ?? (this.#side === "left" ? spread.left ?? spread.right : spread.right ?? spread.left);
return this.book.sections.indexOf(section);
}
#reportLocation(reason) {
this.dispatchEvent(new CustomEvent("relocate", { detail: { reason, range: null, index: this.index, fraction: 0, size: 1 } }));
}
getSpreadOf(section) {
const spreads = this.#spreads;
for (let index = 0; index < spreads.length; index++) {
const { left, right, center } = spreads[index];
if (left === section)
return { index, side: "left" };
if (right === section)
return { index, side: "right" };
if (center === section)
return { index, side: "center" };
}
}
async goToSpread(index, side, reason) {
var _a, _b, _c, _d, _e, _f;
if (index < 0 || index > this.#spreads.length - 1)
return;
if (index === this.#index) {
this.#render(side);
return;
}
this.#index = index;
const spread = this.#spreads[index];
if (spread.center) {
const index2 = this.book.sections.indexOf(spread.center);
const src = await ((_b = (_a = spread.center) == null ? void 0 : _a.load) == null ? void 0 : _b.call(_a));
await this.#showSpread({ center: { index: index2, src } });
} else {
const indexL = this.book.sections.indexOf(spread.left);
const indexR = this.book.sections.indexOf(spread.right);
const srcL = await ((_d = (_c = spread.left) == null ? void 0 : _c.load) == null ? void 0 : _d.call(_c));
const srcR = await ((_f = (_e = spread.right) == null ? void 0 : _e.load) == null ? void 0 : _f.call(_e));
const left = { index: indexL, src: srcL };
const right = { index: indexR, src: srcR };
await this.#showSpread({ left, right, side });
}
this.#reportLocation(reason);
}
async select(target) {
await this.goTo(target);
}
async goTo(target) {
const { book } = this;
const resolved = await target;
const section = book.sections[resolved.index];
if (!section)
return;
const { index, side } = this.getSpreadOf(section);
await this.goToSpread(index, side);
}
async next() {
const s = this.rtl ? this.#goLeft() : this.#goRight();
if (!s)
return this.goToSpread(this.#index + 1, this.rtl ? "right" : "left", "page");
}
async prev() {
const s = this.rtl ? this.#goRight() : this.#goLeft();
if (!s)
return this.goToSpread(this.#index - 1, this.rtl ? "left" : "right", "page");
}
getContents() {
return Array.from(this.#root.querySelectorAll("iframe"), (frame) => ({
doc: frame.contentDocument
// TODO: index, overlayer
}));
}
destroy() {
this.#observer.unobserve(this);
}
}
customElements.define("foliate-fxl", FixedLayout);
exports.FixedLayout = FixedLayout;