vue-book-reader
Version:
vue-book-reader is a vue wrapper for [foliate-js](https://github.com/johnfactotum/foliate-js) - library for rendering e-books in the browser. Supports EPUB, MOBI, KF8 (AZW3), FB2, CBZ, PDF (experimental; requires PDF.js), or add support for other formats
1,038 lines (1,037 loc) • 34.4 kB
JavaScript
var Ft = Object.defineProperty;
var _t = (r, e, t) => e in r ? Ft(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
var q = (r, e, t) => (_t(r, typeof e != "symbol" ? e + "" : e, t), t), Rt = (r, e, t) => {
if (!e.has(r))
throw TypeError("Cannot " + t);
};
var c = (r, e, t) => (Rt(r, e, "read from private field"), t ? t.call(r) : e.get(r)), w = (r, e, t) => {
if (e.has(r))
throw TypeError("Cannot add the same private member more than once");
e instanceof WeakSet ? e.add(r) : e.set(r, t);
}, S = (r, e, t, s) => (Rt(r, e, "write to private field"), s ? s.call(r, t) : e.set(r, t), t);
var Et = (r, e, t, s) => ({
set _(n) {
S(r, e, n, t);
},
get _() {
return c(r, e, s);
}
}), V = (r, e, t) => (Rt(r, e, "access private method"), t);
const M = (r) => {
if (!r)
return "";
const e = document.createElement("textarea");
return e.innerHTML = r, e.value;
}, I = {
XML: "application/xml",
XHTML: "application/xhtml+xml",
HTML: "text/html",
CSS: "text/css",
SVG: "image/svg+xml"
}, Nt = {
name: [0, 32, "string"],
type: [60, 4, "string"],
creator: [64, 4, "string"],
numRecords: [76, 2, "uint"]
}, zt = {
compression: [0, 2, "uint"],
numTextRecords: [8, 2, "uint"],
recordSize: [10, 2, "uint"],
encryption: [12, 2, "uint"]
}, Xt = {
magic: [16, 4, "string"],
length: [20, 4, "uint"],
type: [24, 4, "uint"],
encoding: [28, 4, "uint"],
uid: [32, 4, "uint"],
version: [36, 4, "uint"],
titleOffset: [84, 4, "uint"],
titleLength: [88, 4, "uint"],
localeRegion: [94, 1, "uint"],
localeLanguage: [95, 1, "uint"],
resourceStart: [108, 4, "uint"],
huffcdic: [112, 4, "uint"],
numHuffcdic: [116, 4, "uint"],
exthFlag: [128, 4, "uint"],
trailingFlags: [240, 4, "uint"],
indx: [244, 4, "uint"]
}, Pt = {
resourceStart: [108, 4, "uint"],
fdst: [192, 4, "uint"],
numFdst: [196, 4, "uint"],
frag: [248, 4, "uint"],
skel: [252, 4, "uint"],
guide: [260, 4, "uint"]
}, Gt = {
magic: [0, 4, "string"],
length: [4, 4, "uint"],
count: [8, 4, "uint"]
}, vt = {
magic: [0, 4, "string"],
length: [4, 4, "uint"],
type: [8, 4, "uint"],
idxt: [20, 4, "uint"],
numRecords: [24, 4, "uint"],
encoding: [28, 4, "uint"],
language: [32, 4, "uint"],
total: [36, 4, "uint"],
ordt: [40, 4, "uint"],
ligt: [44, 4, "uint"],
numLigt: [48, 4, "uint"],
numCncx: [52, 4, "uint"]
}, jt = {
magic: [0, 4, "string"],
length: [4, 4, "uint"],
numControlBytes: [8, 4, "uint"]
}, $t = {
magic: [0, 4, "string"],
offset1: [8, 4, "uint"],
offset2: [12, 4, "uint"]
}, qt = {
magic: [0, 4, "string"],
length: [4, 4, "uint"],
numEntries: [8, 4, "uint"],
codeLength: [12, 4, "uint"]
}, Vt = {
magic: [0, 4, "string"],
numEntries: [8, 4, "uint"]
}, Kt = {
flags: [8, 4, "uint"],
dataStart: [12, 4, "uint"],
keyLength: [16, 4, "uint"],
keyStart: [20, 4, "uint"]
}, Yt = {
1252: "windows-1252",
65001: "utf-8"
}, It = {
100: ["creator", "string", !0],
101: ["publisher"],
103: ["description"],
104: ["isbn"],
105: ["subject", "string", !0],
106: ["date"],
108: ["contributor", "string", !0],
109: ["rights"],
110: ["subjectCode", "string", !0],
112: ["source", "string", !0],
113: ["asin"],
121: ["boundary", "uint"],
122: ["fixedLayout"],
125: ["numResources", "uint"],
126: ["originalResolution"],
127: ["zeroGutter"],
128: ["zeroMargin"],
129: ["coverURI"],
132: ["regionMagnification"],
201: ["coverOffset", "uint"],
202: ["thumbnailOffset", "uint"],
503: ["title"],
524: ["language", "string", !0],
527: ["pageProgressionDirection"]
}, Zt = {
1: [
"ar",
"ar-SA",
"ar-IQ",
"ar-EG",
"ar-LY",
"ar-DZ",
"ar-MA",
"ar-TN",
"ar-OM",
"ar-YE",
"ar-SY",
"ar-JO",
"ar-LB",
"ar-KW",
"ar-AE",
"ar-BH",
"ar-QA"
],
2: ["bg"],
3: ["ca"],
4: ["zh", "zh-TW", "zh-CN", "zh-HK", "zh-SG"],
5: ["cs"],
6: ["da"],
7: ["de", "de-DE", "de-CH", "de-AT", "de-LU", "de-LI"],
8: ["el"],
9: [
"en",
"en-US",
"en-GB",
"en-AU",
"en-CA",
"en-NZ",
"en-IE",
"en-ZA",
"en-JM",
null,
"en-BZ",
"en-TT",
"en-ZW",
"en-PH"
],
10: [
"es",
"es-ES",
"es-MX",
null,
"es-GT",
"es-CR",
"es-PA",
"es-DO",
"es-VE",
"es-CO",
"es-PE",
"es-AR",
"es-EC",
"es-CL",
"es-UY",
"es-PY",
"es-BO",
"es-SV",
"es-HN",
"es-NI",
"es-PR"
],
11: ["fi"],
12: ["fr", "fr-FR", "fr-BE", "fr-CA", "fr-CH", "fr-LU", "fr-MC"],
13: ["he"],
14: ["hu"],
15: ["is"],
16: ["it", "it-IT", "it-CH"],
17: ["ja"],
18: ["ko"],
19: ["nl", "nl-NL", "nl-BE"],
20: ["no", "nb", "nn"],
21: ["pl"],
22: ["pt", "pt-BR", "pt-PT"],
23: ["rm"],
24: ["ro"],
25: ["ru"],
26: ["hr", null, "sr"],
27: ["sk"],
28: ["sq"],
29: ["sv", "sv-SE", "sv-FI"],
30: ["th"],
31: ["tr"],
32: ["ur"],
33: ["id"],
34: ["uk"],
35: ["be"],
36: ["sl"],
37: ["et"],
38: ["lv"],
39: ["lt"],
41: ["fa"],
42: ["vi"],
43: ["hy"],
44: ["az"],
45: ["eu"],
46: ["hsb"],
47: ["mk"],
48: ["st"],
49: ["ts"],
50: ["tn"],
52: ["xh"],
53: ["zu"],
54: ["af"],
55: ["ka"],
56: ["fo"],
57: ["hi"],
58: ["mt"],
59: ["se"],
62: ["ms"],
63: ["kk"],
65: ["sw"],
67: ["uz", null, "uz-UZ"],
68: ["tt"],
69: ["bn"],
70: ["pa"],
71: ["gu"],
72: ["or"],
73: ["ta"],
74: ["te"],
75: ["kn"],
76: ["ml"],
77: ["as"],
78: ["mr"],
79: ["sa"],
82: ["cy", "cy-GB"],
83: ["gl", "gl-ES"],
87: ["kok"],
97: ["ne"],
98: ["fy"]
}, mt = (r, e) => {
const t = new r.constructor(r.length + e.length);
return t.set(r), t.set(e, r.length), t;
}, kt = (r, e, t) => {
const s = new r.constructor(r.length + e.length + t.length);
return s.set(r), s.set(e, r.length), s.set(t, r.length + e.length), s;
}, Wt = new TextDecoder(), ft = (r) => Wt.decode(r), A = (r) => {
if (!r)
return;
const e = r.byteLength, t = e === 4 ? "getUint32" : e === 2 ? "getUint16" : "getUint8";
return new DataView(r)[t](0);
}, T = (r, e) => Object.fromEntries(Array.from(Object.entries(r)).map(([t, [s, n, i]]) => [
t,
(i === "string" ? ft : A)(e.slice(s, s + n))
])), Tt = (r) => new TextDecoder(Yt[r]), dt = (r, e = 0) => {
let t = 0, s = 0;
for (const n of r.subarray(e, e + 4))
if (t = t << 7 | (n & 127) >>> 0, s++, n & 128)
break;
return { value: t, length: s };
}, Jt = (r) => {
let e = 0;
for (const t of r.subarray(-4))
t & 128 && (e = 0), e = e << 7 | t & 127;
return e;
}, Ut = (r) => {
let e = 0;
for (; r > 0; r = r >> 1)
(r & 1) === 1 && e++;
return e;
}, Qt = (r) => {
let e = 0;
for (; !(r & 1); )
r = r >> 1, e++;
return e;
}, te = (r) => {
let e = [];
for (let t = 0; t < r.length; t++) {
const s = r[t];
if (s === 0)
e.push(0);
else if (s <= 8)
for (const n of r.subarray(t + 1, (t += s) + 1))
e.push(n);
else if (s <= 127)
e.push(s);
else if (s <= 191) {
const n = s << 8 | r[t++ + 1], i = (n & 16383) >>> 3, o = (n & 7) + 3;
for (let a = 0; a < o; a++)
e.push(e[e.length - i]);
} else
e.push(32, s ^ 128);
}
return Uint8Array.from(e);
}, ee = (r, e) => {
const t = e >> 3, s = e + 32, n = s >> 3;
let i = 0n;
for (let o = t; o <= n; o++)
i = i << 8n | BigInt(r[o] ?? 0);
return i >> 8n - BigInt(s & 7) & 0xffffffffn;
}, se = async (r, e) => {
const t = await e(r.huffcdic), { magic: s, offset1: n, offset2: i } = T($t, t);
if (s !== "HUFF")
throw new Error("Invalid HUFF record");
const o = Array.from({ length: 256 }, (h, b) => n + b * 4).map((h) => A(t.slice(h, h + 4))).map((h) => [h & 128, h & 31, h >>> 8]), a = [null].concat(Array.from({ length: 32 }, (h, b) => i + b * 8).map((h) => [
A(t.slice(h, h + 4)),
A(t.slice(h + 4, h + 8))
])), u = [];
for (let h = 1; h < r.numHuffcdic; h++) {
const b = await e(r.huffcdic + h), p = T(qt, b);
if (p.magic !== "CDIC")
throw new Error("Invalid CDIC record");
const l = Math.min(1 << p.codeLength, p.numEntries - u.length), f = b.slice(p.length);
for (let d = 0; d < l; d++) {
const m = A(f.slice(d * 2, d * 2 + 2)), y = A(f.slice(m, m + 2)), R = y & 32767, E = y & 32768, v = new Uint8Array(
f.slice(m + 2, m + 2 + R)
);
u.push([v, E]);
}
}
const g = (h) => {
let b = new Uint8Array();
const p = h.byteLength * 8;
for (let l = 0; l < p; ) {
const f = Number(ee(h, l));
let [d, m, y] = o[f >>> 24];
if (!d) {
for (; f >>> 32 - m < a[m][0]; )
m += 1;
y = a[m][1];
}
if ((l += m) > p)
break;
const R = y - (f >>> 32 - m);
let [E, v] = u[R];
v || (E = g(E), u[R] = [E, !0]), b = mt(b, E);
}
return b;
};
return g;
}, gt = async (r, e) => {
const t = await e(r), s = T(vt, t);
if (s.magic !== "INDX")
throw new Error("Invalid INDX record");
const n = Tt(s.encoding), i = t.slice(s.length), o = T(jt, i);
if (o.magic !== "TAGX")
throw new Error("Invalid TAGX section");
const a = (o.length - 12) / 4, u = Array.from({ length: a }, (p, l) => new Uint8Array(i.slice(12 + l * 4, 12 + l * 4 + 4))), g = {};
let h = 0;
for (let p = 0; p < s.numCncx; p++) {
const l = await e(r + s.numRecords + p + 1), f = new Uint8Array(l);
for (let d = 0; d < f.byteLength; ) {
const m = d, { value: y, length: R } = dt(f, d);
d += R;
const E = l.slice(d, d + y);
d += y, g[h + m] = n.decode(E);
}
h += 65536;
}
const b = [];
for (let p = 0; p < s.numRecords; p++) {
const l = await e(r + 1 + p), f = new Uint8Array(l), d = T(vt, l);
if (d.magic !== "INDX")
throw new Error("Invalid INDX record");
for (let m = 0; m < d.numRecords; m++) {
const y = d.idxt + 4 + 2 * m, R = A(l.slice(y, y + 2)), E = A(l.slice(R, R + 1)), v = ft(l.slice(R + 1, R + 1 + E)), x = [], ht = R + 1 + E;
let xt = 0, X = ht + o.numControlBytes;
for (const [tt, P, G, yt] of u) {
if (yt & 1) {
xt++;
continue;
}
const j = ht + xt, B = A(l.slice(j, j + 1)) & G;
if (B === G)
if (Ut(G) > 1) {
const { value: et, length: $ } = dt(f, X);
x.push([tt, null, et, P]), X += $;
} else
x.push([tt, 1, null, P]);
else
x.push([tt, B >> Qt(G), null, P]);
}
const Lt = {};
for (const [tt, P, G, yt] of x) {
const j = [];
if (P != null)
for (let B = 0; B < P * yt; B++) {
const { value: et, length: $ } = dt(f, X);
j.push(et), X += $;
}
else {
let B = 0;
for (; B < G; ) {
const { value: et, length: $ } = dt(f, X);
j.push(et), X += $, B += $;
}
}
Lt[tt] = j;
}
b.push({ name: v, tagMap: Lt });
}
}
return { table: b, cncx: g };
}, ne = async (r, e) => {
const { table: t, cncx: s } = await gt(r, e), n = t.map(({ tagMap: o }, a) => {
var u, g, h, b, p, l;
return {
index: a,
offset: (u = o[1]) == null ? void 0 : u[0],
size: (g = o[2]) == null ? void 0 : g[0],
label: s[o[3]] ?? "",
headingLevel: (h = o[4]) == null ? void 0 : h[0],
pos: o[6],
parent: (b = o[21]) == null ? void 0 : b[0],
firstChild: (p = o[22]) == null ? void 0 : p[0],
lastChild: (l = o[23]) == null ? void 0 : l[0]
};
}), i = (o) => (o.firstChild == null || (o.children = n.filter((a) => a.parent === o.index).map(i)), o);
return n.filter((o) => o.headingLevel === 0).map(i);
}, re = (r, e) => {
const { magic: t, count: s } = T(Gt, r);
if (t !== "EXTH")
throw new Error("Invalid EXTH header");
const n = Tt(e), i = {};
let o = 12;
for (let a = 0; a < s; a++) {
const u = A(r.slice(o, o + 4)), g = A(r.slice(o + 4, o + 8));
if (u in It) {
const [h, b, p] = It[u], l = r.slice(o + 8, o + g), f = b === "uint" ? A(l) : n.decode(l);
p ? (i[h] ?? (i[h] = []), i[h].push(f)) : i[h] = f;
}
o += g;
}
return i;
}, ie = async (r, e) => {
const { flags: t, dataStart: s, keyLength: n, keyStart: i } = T(Kt, r), o = new Uint8Array(r.slice(s));
if (t & 2) {
const u = n === 16 ? 1024 : 1040, g = new Uint8Array(r.slice(i, i + n)), h = Math.min(u, o.length);
for (var a = 0; a < h; a++)
o[a] = o[a] ^ g[a % g.length];
}
if (t & 1)
try {
return await e(o);
} catch (u) {
console.warn(u), console.warn("Failed to decompress font");
}
return o;
}, pe = async (r) => ft(await r.slice(60, 68).arrayBuffer()) === "BOOKMOBI";
var K, Y;
class oe {
constructor() {
w(this, K, void 0);
w(this, Y, void 0);
q(this, "pdb");
}
async open(e) {
S(this, K, e);
const t = T(Nt, await e.slice(0, 78).arrayBuffer());
this.pdb = t;
const s = await e.slice(78, 78 + t.numRecords * 8).arrayBuffer();
S(this, Y, Array.from(
{ length: t.numRecords },
(n, i) => A(s.slice(i * 8, i * 8 + 4))
).map((n, i, o) => [n, o[i + 1]]));
}
loadRecord(e) {
const t = c(this, Y)[e];
if (!t)
throw new RangeError("Record index out of bounds");
return c(this, K).slice(...t).arrayBuffer();
}
async loadMagic(e) {
const t = c(this, Y)[e][0];
return ft(await c(this, K).slice(t, t + 4).arrayBuffer());
}
}
K = new WeakMap(), Y = new WeakMap();
var Z, st, nt, rt, W, it, ot, St, pt, Mt;
class be extends oe {
constructor({ unzlib: t }) {
super();
w(this, ot);
w(this, pt);
w(this, Z, 0);
w(this, st, void 0);
w(this, nt, void 0);
w(this, rt, void 0);
w(this, W, void 0);
w(this, it, void 0);
this.unzlib = t;
}
async open(t) {
var n;
await super.open(t), this.headers = V(this, ot, St).call(this, await super.loadRecord(0)), S(this, st, this.headers.mobi.resourceStart);
let s = this.headers.mobi.version >= 8;
if (!s) {
const i = (n = this.headers.exth) == null ? void 0 : n.boundary;
if (i < 4294967295)
try {
this.headers = V(this, ot, St).call(this, await super.loadRecord(i)), S(this, Z, i), s = !0;
} catch (o) {
console.warn(o), console.warn("Failed to open KF8; falling back to MOBI");
}
}
return await V(this, pt, Mt).call(this), s ? new ge(this).init() : new le(this).init();
}
decode(...t) {
return c(this, nt).decode(...t);
}
encode(...t) {
return c(this, rt).encode(...t);
}
loadRecord(t) {
return super.loadRecord(c(this, Z) + t);
}
loadMagic(t) {
return super.loadMagic(c(this, Z) + t);
}
loadText(t) {
return this.loadRecord(t + 1).then((s) => new Uint8Array(s)).then(c(this, it)).then(c(this, W));
}
async loadResource(t) {
const s = await super.loadRecord(c(this, st) + t), n = ft(s.slice(0, 4));
return n === "FONT" ? ie(s, this.unzlib) : n === "VIDE" || n === "AUDI" ? s.slice(12) : s;
}
getNCX() {
const t = this.headers.mobi.indx;
if (t < 4294967295)
return ne(t, this.loadRecord.bind(this));
}
getMetadata() {
var n, i;
const { mobi: t, exth: s } = this.headers;
return {
identifier: t.uid.toString(),
title: M((s == null ? void 0 : s.title) || this.decode(t.title)),
author: (n = s == null ? void 0 : s.creator) == null ? void 0 : n.map(M),
publisher: M(s == null ? void 0 : s.publisher),
language: (s == null ? void 0 : s.language) ?? t.language,
published: s == null ? void 0 : s.date,
description: M(s == null ? void 0 : s.description),
subject: (i = s == null ? void 0 : s.subject) == null ? void 0 : i.map(M),
rights: M(s == null ? void 0 : s.rights),
contributor: s == null ? void 0 : s.contributor
};
}
async getCover() {
const { exth: t } = this.headers, s = (t == null ? void 0 : t.coverOffset) < 4294967295 ? t == null ? void 0 : t.coverOffset : (t == null ? void 0 : t.thumbnailOffset) < 4294967295 ? t == null ? void 0 : t.thumbnailOffset : null;
if (s != null) {
const n = await this.loadResource(s);
return new Blob([n]);
}
}
}
Z = new WeakMap(), st = new WeakMap(), nt = new WeakMap(), rt = new WeakMap(), W = new WeakMap(), it = new WeakMap(), ot = new WeakSet(), St = function(t) {
const s = T(zt, t), n = T(Xt, t);
if (n.magic !== "MOBI")
throw new Error("Missing MOBI header");
const { titleOffset: i, titleLength: o, localeLanguage: a, localeRegion: u } = n;
n.title = t.slice(i, i + o);
const g = Zt[a];
n.language = (g == null ? void 0 : g[u >> 2]) ?? (g == null ? void 0 : g[0]);
const h = n.exthFlag & 64 ? re(t.slice(n.length + 16), n.encoding) : null, b = n.version >= 8 ? T(Pt, t) : null;
return { palmdoc: s, mobi: n, exth: h, kf8: b };
}, pt = new WeakSet(), Mt = async function() {
const { palmdoc: t, mobi: s } = this.headers;
S(this, nt, Tt(s.encoding)), S(this, rt, new TextEncoder());
const { compression: n } = t;
if (S(this, W, n === 1 ? (u) => u : n === 2 ? te : n === 17480 ? await se(s, this.loadRecord.bind(this)) : null), !c(this, W))
throw new Error("Unknown compression type");
const { trailingFlags: i } = s, o = i & 1, a = Ut(i >>> 1);
S(this, it, (u) => {
for (let g = 0; g < a; g++) {
const h = Jt(u);
u = u.subarray(0, -h);
}
if (o) {
const g = (u[u.length - 1] & 3) + 1;
u = u.subarray(0, -g);
}
return u;
});
};
const Ct = /<\s*(?:mbp:)?pagebreak[^>]*>/gi, ce = /<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>/gi, ae = (r) => {
let e = 0;
for (; r; ) {
const t = r.parentElement;
if (t) {
const s = t.tagName.toLowerCase();
s === "p" ? e += 1.5 : s === "blockquote" && (e += 2);
}
r = t;
}
return e;
};
var H, J, F, D, ct, at;
class le {
constructor(e) {
q(this, "parser", new DOMParser());
q(this, "serializer", new XMLSerializer());
w(this, H, /* @__PURE__ */ new Map());
w(this, J, /* @__PURE__ */ new Map());
w(this, F, /* @__PURE__ */ new Map());
w(this, D, void 0);
w(this, ct, []);
w(this, at, I.HTML);
this.mobi = e;
}
async init() {
var s;
let e = new Uint8Array();
for (let n = 0; n < this.mobi.headers.palmdoc.numTextRecords; n++)
e = mt(e, await this.mobi.loadText(n));
const t = Array.from(
new Uint8Array(e),
(n) => String.fromCharCode(n)
).join("");
S(this, D, [0].concat(Array.from(t.matchAll(Ct), (n) => n.index)).map((n, i, o) => t.slice(n, o[i + 1])).map((n) => Uint8Array.from(n, (i) => i.charCodeAt(0))).map((n) => ({ book: this, raw: n })).reduce((n, i) => {
const o = n[n.length - 1];
return i.start = (o == null ? void 0 : o.end) ?? 0, i.end = i.start + i.raw.byteLength, n.concat(i);
}, [])), this.sections = c(this, D).map((n, i) => ({
id: i,
load: () => this.loadSection(n),
createDocument: () => this.createDocument(n),
size: n.end - n.start
}));
try {
this.landmarks = await this.getGuide();
const n = (s = this.landmarks.find(({ type: i }) => i == null ? void 0 : i.includes("toc"))) == null ? void 0 : s.href;
if (n) {
const { index: i } = this.resolveHref(n), o = await this.sections[i].createDocument();
let a, u = 0, g = 0;
const h = /* @__PURE__ */ new Map(), b = /* @__PURE__ */ new Map();
this.toc = Array.from(o.querySelectorAll("a[filepos]")).reduce((p, l) => {
var y;
const f = ae(l), d = {
label: ((y = l.innerText) == null ? void 0 : y.trim()) ?? "",
href: `filepos:${l.getAttribute("filepos")}`
}, m = f > g ? u + 1 : f === g ? u : h.get(f) ?? Math.max(0, u - 1);
if (m > u)
a ? (a.subitems ?? (a.subitems = []), a.subitems.push(d), b.set(m, a)) : p.push(d);
else {
const R = b.get(m);
R ? R.subitems.push(d) : p.push(d);
}
return a = d, u = m, g = f, h.set(f, m), p;
}, []);
}
} catch (n) {
console.warn(n);
}
return S(this, ct, [...new Set(
Array.from(t.matchAll(ce), (n) => n[1])
)].map((n) => ({ filepos: n, number: Number(n) })).sort((n, i) => n.number - i.number)), this.metadata = this.mobi.getMetadata(), this.getCover = this.mobi.getCover.bind(this.mobi), this;
}
async getGuide() {
const e = await this.createDocument(c(this, D)[0]);
return Array.from(e.getElementsByTagName("reference"), (t) => {
var s;
return {
label: t.getAttribute("title"),
type: (s = t.getAttribute("type")) == null ? void 0 : s.split(/\s/),
href: `filepos:${t.getAttribute("filepos")}`
};
});
}
async loadResource(e) {
if (c(this, H).has(e))
return c(this, H).get(e);
const t = await this.mobi.loadResource(e), s = URL.createObjectURL(new Blob([t]));
return c(this, H).set(e, s), s;
}
async loadRecindex(e) {
return this.loadResource(Number(e) - 1);
}
async replaceResources(e) {
for (const t of e.querySelectorAll("img[recindex]")) {
const s = t.getAttribute("recindex");
try {
t.src = await this.loadRecindex(s);
} catch {
console.warn(`Failed to load image ${s}`);
}
}
for (const t of e.querySelectorAll("[mediarecindex]")) {
const s = t.getAttribute("mediarecindex"), n = t.getAttribute("recindex");
try {
t.src = await this.loadRecindex(s), n && (t.poster = await this.loadRecindex(n));
} catch {
console.warn(`Failed to load media ${s}`);
}
}
for (const t of e.querySelectorAll("[filepos]")) {
const s = t.getAttribute("filepos");
t.href = `filepos:${s}`;
}
}
async loadText(e) {
if (c(this, J).has(e))
return c(this, J).get(e);
const { raw: t } = e, s = c(this, ct).filter(({ number: o }) => o >= e.start && o < e.end).map((o) => ({ ...o, offset: o.number - e.start }));
let n = t;
s.length && (n = t.subarray(0, s[0].offset), s.forEach(({ filepos: o, offset: a }, u) => {
const g = s[u + 1], h = this.mobi.encode(`<a id="filepos${o}"></a>`);
n = kt(n, h, t.subarray(a, g == null ? void 0 : g.offset));
}));
const i = this.mobi.decode(n).replaceAll(Ct, "");
return c(this, J).set(e, i), i;
}
async createDocument(e) {
const t = await this.loadText(e);
return this.parser.parseFromString(t, c(this, at));
}
async loadSection(e) {
if (c(this, F).has(e))
return c(this, F).get(e);
const t = await this.createDocument(e), s = t.createElement("style");
t.head.append(s), s.append(t.createTextNode(`blockquote {
margin-block-start: 0;
margin-block-end: 0;
margin-inline-start: 1em;
margin-inline-end: 0;
}`)), await this.replaceResources(t);
const n = this.serializer.serializeToString(t), i = URL.createObjectURL(new Blob([n], { type: c(this, at) }));
return c(this, F).set(e, i), i;
}
resolveHref(e) {
const t = e.match(/filepos:(.*)/)[1], s = Number(t);
return { index: c(this, D).findIndex((o) => o.end > s), anchor: (o) => o.getElementById(`filepos${t}`) };
}
splitTOCHref(e) {
const t = e.match(/filepos:(.*)/)[1], s = Number(t);
return [c(this, D).findIndex((i) => i.end > s), `filepos${t}`];
}
getTOCFragment(e, t) {
return e.getElementById(t);
}
isExternal(e) {
return /^(?!blob|filepos)\w+:/i.test(e);
}
destroy() {
for (const e of c(this, H).values())
URL.revokeObjectURL(e);
for (const e of c(this, F).values())
URL.revokeObjectURL(e);
}
}
H = new WeakMap(), J = new WeakMap(), F = new WeakMap(), D = new WeakMap(), ct = new WeakMap(), at = new WeakMap();
const Ht = /kindle:(flow|embed):(\w+)(?:\?mime=(\w+\/[-+.\w]+))?/, ue = /kindle:pos:fid:(\w+):off:(\w+)/, fe = (r) => {
const [e, t, s] = r.match(Ht).slice(1);
return { resourceType: e, id: parseInt(t, 32), type: s };
}, Bt = (r) => {
const [e, t] = r.match(ue).slice(1);
return { fid: parseInt(e, 32), off: parseInt(t, 32) };
}, Dt = (r = 0, e = 0) => `kindle:pos:fid:${r.toString(32).toUpperCase().padStart(4, "0")}:off:${e.toString(32).toUpperCase().padStart(10, "0")}`, Ot = (r) => {
const e = r.match(/\s(id|name|aid)\s*=\s*['"]([^'"]*)['"]/i);
if (!e)
return;
const [, t, s] = e;
return `[${t}="${CSS.escape(s)}"]`;
}, he = async (r, e, t) => {
const s = [];
r.replace(e, (...i) => (s.push(i), null));
const n = [];
for (const i of s)
n.push(await t(...i));
return r.replace(e, () => n.shift());
}, de = (r) => {
for (const e of r) {
if (e === "page-spread-left" || e === "rendition:page-spread-left")
return "left";
if (e === "page-spread-right" || e === "rendition:page-spread-right")
return "right";
if (e === "rendition:page-spread-center")
return "center";
}
};
var L, Q, _, N, z, O, k, C, bt, wt, U, lt, ut, At;
class ge {
constructor(e) {
w(this, ut);
q(this, "parser", new DOMParser());
q(this, "serializer", new XMLSerializer());
w(this, L, /* @__PURE__ */ new Map());
w(this, Q, /* @__PURE__ */ new Map());
w(this, _, /* @__PURE__ */ new Map());
w(this, N, {});
w(this, z, void 0);
w(this, O, void 0);
w(this, k, new Uint8Array());
w(this, C, new Uint8Array());
w(this, bt, -1);
w(this, wt, -1);
w(this, U, I.XHTML);
w(this, lt, /* @__PURE__ */ new Map());
this.mobi = e;
}
async init() {
var g, h, b, p;
const e = this.mobi.loadRecord.bind(this.mobi), { kf8: t } = this.mobi.headers;
try {
const l = await e(t.fdst), f = T(Vt, l);
if (f.magic !== "FDST")
throw new Error("Missing FDST record");
const d = Array.from(
{ length: f.numEntries },
(m, y) => 12 + y * 8
).map((m) => [
A(l.slice(m, m + 4)),
A(l.slice(m + 4, m + 8))
]);
c(this, N).fdstTable = d, S(this, O, d[d.length - 1][1]);
} catch {
}
const s = (await gt(t.skel, e)).table.map(({ name: l, tagMap: f }, d) => ({
index: d,
name: l,
numFrag: f[1][0],
offset: f[6][0],
length: f[6][1]
})), n = await gt(t.frag, e), i = n.table.map(({ name: l, tagMap: f }) => ({
insertOffset: parseInt(l),
selector: n.cncx[f[2][0]],
index: f[4][0],
offset: f[6][0],
length: f[6][1]
}));
c(this, N).skelTable = s, c(this, N).fragTable = i, S(this, z, s.reduce((l, f) => {
const d = l[l.length - 1], m = (d == null ? void 0 : d.fragEnd) ?? 0, y = m + f.numFrag, R = i.slice(m, y), E = f.length + R.map((x) => x.length).reduce((x, ht) => x + ht), v = ((d == null ? void 0 : d.totalLength) ?? 0) + E;
return l.concat({ skel: f, frags: R, fragEnd: y, length: E, totalLength: v });
}, []));
const o = await this.getResourcesByMagic(["RESC", "PAGE"]), a = /* @__PURE__ */ new Map();
if (o.RESC) {
const l = await this.mobi.loadRecord(o.RESC), f = this.mobi.decode(l.slice(16)).replace(/\0/g, ""), d = f.search(/\?>/), m = `<package>${f.slice(d)}</package>`, y = this.parser.parseFromString(m, I.XML);
for (const R of y.querySelectorAll("spine > itemref")) {
const E = parseInt(R.getAttribute("skelid"));
a.set(E, de(
((g = R.getAttribute("properties")) == null ? void 0 : g.split(" ")) ?? []
));
}
}
this.sections = c(this, z).map((l, f) => l.frags.length ? {
id: f,
load: () => this.loadSection(l),
createDocument: () => this.createDocument(l),
size: l.length,
pageSpread: a.get(f)
} : { linear: "no" });
try {
const l = await this.mobi.getNCX(), f = ({ label: d, pos: m, children: y }) => {
const [R, E] = m, v = Dt(R, E), x = c(this, Q).get(R);
return x ? x.push(E) : c(this, Q).set(R, [E]), { label: M(d), href: v, subitems: y == null ? void 0 : y.map(f) };
};
this.toc = l == null ? void 0 : l.map(f), this.landmarks = await this.getGuide();
} catch (l) {
console.warn(l);
}
const { exth: u } = this.mobi.headers;
return this.dir = u.pageProgressionDirection, this.rendition = {
layout: u.fixedLayout === "true" ? "pre-paginated" : "reflowable",
viewport: Object.fromEntries(((p = (b = (h = u.originalResolution) == null ? void 0 : h.split("x")) == null ? void 0 : b.slice(0, 2)) == null ? void 0 : p.map((l, f) => [f ? "height" : "width", l])) ?? [])
}, this.metadata = this.mobi.getMetadata(), this.getCover = this.mobi.getCover.bind(this.mobi), this;
}
// is this really the only way of getting to RESC, PAGE, etc.?
async getResourcesByMagic(e) {
const t = {}, s = this.mobi.headers.kf8.resourceStart, n = this.mobi.pdb.numRecords;
for (let i = s; i < n; i++)
try {
const o = await this.mobi.loadMagic(i), a = e.find((u) => u === o);
a && (t[a] = i);
} catch {
}
return t;
}
async getGuide() {
const e = this.mobi.headers.kf8.guide;
if (e < 4294967295) {
const t = this.mobi.loadRecord.bind(this.mobi), { table: s, cncx: n } = await gt(e, t);
return s.map(({ name: i, tagMap: o }) => {
var a, u;
return {
label: n[o[1][0]] ?? "",
type: i == null ? void 0 : i.split(/\s/),
href: Dt(((a = o[6]) == null ? void 0 : a[0]) ?? ((u = o[3]) == null ? void 0 : u[0]))
};
});
}
}
async loadResourceBlob(e) {
var u;
const { resourceType: t, id: s, type: n } = fe(e), i = t === "flow" ? await this.loadFlow(s) : await this.mobi.loadResource(s - 1), o = [I.XHTML, I.HTML, I.CSS, I.SVG].includes(n) ? await this.replaceResources(this.mobi.decode(i)) : i, a = n === I.SVG ? this.parser.parseFromString(o, n) : null;
return [
new Blob([o], { type: n }),
// SVG wrappers need to be inlined
// as browsers don't allow external resources when loading SVG as an image
(u = a == null ? void 0 : a.getElementsByTagNameNS("http://www.w3.org/2000/svg", "image")) != null && u.length ? a.documentElement : null
];
}
async loadResource(e) {
if (c(this, L).has(e))
return c(this, L).get(e);
const [t, s] = await this.loadResourceBlob(e), n = s ? e : URL.createObjectURL(t);
return s && c(this, lt).set(n, s), c(this, L).set(e, n), n;
}
replaceResources(e) {
const t = new RegExp(Ht, "g");
return he(e, t, this.loadResource.bind(this));
}
// NOTE: there doesn't seem to be a way to access text randomly?
// how to know the decompressed size of the records without decompressing?
// 4096 is just the maximum size
async loadRaw(e, t) {
const s = t - c(this, k).length, n = c(this, O) == null ? 1 / 0 : c(this, O) - c(this, C).length - e;
if (s < 0 || s < n) {
for (; c(this, k).length < t; ) {
const o = ++Et(this, bt)._, a = await this.mobi.loadText(o);
S(this, k, mt(c(this, k), a));
}
return c(this, k).slice(e, t);
}
for (; c(this, O) - c(this, C).length > e; ) {
const o = this.mobi.headers.palmdoc.numTextRecords - 1 - ++Et(this, wt)._, a = await this.mobi.loadText(o);
S(this, C, mt(a, c(this, C)));
}
const i = c(this, O) - c(this, C).length;
return c(this, C).slice(e - i, t - i);
}
loadFlow(e) {
if (e < 4294967295)
return this.loadRaw(...c(this, N).fdstTable[e]);
}
async loadText(e) {
const { skel: t, frags: s, length: n } = e, i = await this.loadRaw(t.offset, t.offset + n);
let o = i.slice(0, t.length);
for (const a of s) {
const u = a.insertOffset - t.offset, g = t.length + a.offset, h = i.slice(g, g + a.length);
o = kt(
o.slice(0, u),
h,
o.slice(u)
);
const b = c(this, Q).get(a.index);
if (b)
for (const p of b) {
const l = this.mobi.decode(h).slice(p), f = Ot(l);
V(this, ut, At).call(this, a.index, p, f);
}
}
return this.mobi.decode(o);
}
async createDocument(e) {
const t = await this.loadText(e);
return this.parser.parseFromString(t, c(this, U));
}
async loadSection(e) {
if (c(this, L).has(e))
return c(this, L).get(e);
const t = await this.loadText(e), s = await this.replaceResources(t);
let n = this.parser.parseFromString(s, c(this, U));
n.querySelector("parsererror") && (S(this, U, I.HTML), n = this.parser.parseFromString(s, c(this, U)));
for (const [o, a] of c(this, lt))
for (const u of n.querySelectorAll(`img[src="${o}"]`))
u.replaceWith(a);
const i = URL.createObjectURL(
new Blob([this.serializer.serializeToString(n)], { type: c(this, U) })
);
return c(this, L).set(e, i), i;
}
getIndexByFID(e) {
return c(this, z).findIndex((t) => t.frags.some((s) => s.index === e));
}
async resolveHref(e) {
var f;
const { fid: t, off: s } = Bt(e), n = this.getIndexByFID(t);
if (n < 0)
return;
const i = (f = c(this, _).get(t)) == null ? void 0 : f.get(s);
if (i)
return { index: n, anchor: (d) => d.querySelector(i) };
const { skel: o, frags: a } = c(this, z)[n], u = a.find((d) => d.index === t), g = o.offset + o.length + u.offset, h = await this.loadRaw(g, g + u.length), b = this.mobi.decode(h).slice(s), p = Ot(b);
return V(this, ut, At).call(this, t, s, p), { index: n, anchor: (d) => d.querySelector(p) };
}
splitTOCHref(e) {
const t = Bt(e);
return [this.getIndexByFID(t.fid), t];
}
getTOCFragment(e, { fid: t, off: s }) {
var i;
const n = (i = c(this, _).get(t)) == null ? void 0 : i.get(s);
return e.querySelector(n);
}
isExternal(e) {
return /^(?!blob|kindle)\w+:/i.test(e);
}
destroy() {
for (const e of c(this, L).values())
URL.revokeObjectURL(e);
}
}
L = new WeakMap(), Q = new WeakMap(), _ = new WeakMap(), N = new WeakMap(), z = new WeakMap(), O = new WeakMap(), k = new WeakMap(), C = new WeakMap(), bt = new WeakMap(), wt = new WeakMap(), U = new WeakMap(), lt = new WeakMap(), ut = new WeakSet(), At = function(e, t, s) {
const n = c(this, _).get(e);
if (n)
n.set(t, s);
else {
const i = /* @__PURE__ */ new Map();
c(this, _).set(e, i), i.set(t, s);
}
};
export {
be as MOBI,
pe as isMOBI
};