UNPKG

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
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 };