UNPKG

@triabin/vue-book-reader

Version:

Forked from jinhuan138/vue-book-reader, add some features

969 lines (968 loc) 31.8 kB
const L = (o) => { if (!o) return ""; const t = document.createElement("textarea"); return t.innerHTML = o, t.value; }, T = { XML: "application/xml", XHTML: "application/xhtml+xml", HTML: "text/html", CSS: "text/css", SVG: "image/svg+xml" }, J = { name: [0, 32, "string"], type: [60, 4, "string"], creator: [64, 4, "string"], numRecords: [76, 2, "uint"] }, Q = { compression: [0, 2, "uint"], numTextRecords: [8, 2, "uint"], recordSize: [10, 2, "uint"], encryption: [12, 2, "uint"] }, tt = { 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"] }, et = { resourceStart: [108, 4, "uint"], fdst: [192, 4, "uint"], numFdst: [196, 4, "uint"], frag: [248, 4, "uint"], skel: [252, 4, "uint"], guide: [260, 4, "uint"] }, st = { magic: [0, 4, "string"], length: [4, 4, "uint"], count: [8, 4, "uint"] }, G = { 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"] }, nt = { magic: [0, 4, "string"], length: [4, 4, "uint"], numControlBytes: [8, 4, "uint"] }, rt = { magic: [0, 4, "string"], offset1: [8, 4, "uint"], offset2: [12, 4, "uint"] }, it = { magic: [0, 4, "string"], length: [4, 4, "uint"], numEntries: [8, 4, "uint"], codeLength: [12, 4, "uint"] }, ot = { magic: [0, 4, "string"], numEntries: [8, 4, "uint"] }, at = { flags: [8, 4, "uint"], dataStart: [12, 4, "uint"], keyLength: [16, 4, "uint"], keyStart: [20, 4, "uint"] }, ct = { 1252: "windows-1252", 65001: "utf-8" }, j = { 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"] }, lt = { 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"] }, _ = (o, t) => { const e = new o.constructor(o.length + t.length); return e.set(o), e.set(t, o.length), e; }, Y = (o, t, e) => { const s = new o.constructor(o.length + t.length + e.length); return s.set(o), s.set(t, o.length), s.set(e, o.length + t.length), s; }, ut = new TextDecoder(), U = (o) => ut.decode(o), R = (o) => { if (!o) return; const t = o.byteLength, e = t === 4 ? "getUint32" : t === 2 ? "getUint16" : "getUint8"; return new DataView(o)[e](0); }, E = (o, t) => Object.fromEntries(Array.from(Object.entries(o)).map(([e, [s, n, r]]) => [ e, (r === "string" ? U : R)(t.slice(s, s + n)) ])), z = (o) => new TextDecoder(ct[o]), H = (o, t = 0) => { let e = 0, s = 0; for (const n of o.subarray(t, t + 4)) if (e = e << 7 | (n & 127) >>> 0, s++, n & 128) break; return { value: e, length: s }; }, ft = (o) => { let t = 0; for (const e of o.subarray(-4)) e & 128 && (t = 0), t = t << 7 | e & 127; return t; }, Z = (o) => { let t = 0; for (; o > 0; o = o >> 1) (o & 1) === 1 && t++; return t; }, ht = (o) => { let t = 0; for (; !(o & 1); ) o = o >> 1, t++; return t; }, dt = (o) => { let t = []; for (let e = 0; e < o.length; e++) { const s = o[e]; if (s === 0) t.push(0); else if (s <= 8) for (const n of o.subarray(e + 1, (e += s) + 1)) t.push(n); else if (s <= 127) t.push(s); else if (s <= 191) { const n = s << 8 | o[e++ + 1], r = (n & 16383) >>> 3, i = (n & 7) + 3; for (let a = 0; a < i; a++) t.push(t[t.length - r]); } else t.push(32, s ^ 128); } return Uint8Array.from(t); }, gt = (o, t) => { const e = t >> 3, s = t + 32, n = s >> 3; let r = 0n; for (let i = e; i <= n; i++) r = r << 8n | BigInt(o[i] ?? 0); return r >> 8n - BigInt(s & 7) & 0xffffffffn; }, mt = async (o, t) => { const e = await t(o.huffcdic), { magic: s, offset1: n, offset2: r } = E(rt, e); if (s !== "HUFF") throw new Error("Invalid HUFF record"); const i = Array.from({ length: 256 }, (f, p) => n + p * 4).map((f) => R(e.slice(f, f + 4))).map((f) => [f & 128, f & 31, f >>> 8]), a = [null].concat(Array.from({ length: 32 }, (f, p) => r + p * 8).map((f) => [ R(e.slice(f, f + 4)), R(e.slice(f + 4, f + 8)) ])), l = []; for (let f = 1; f < o.numHuffcdic; f++) { const p = await t(o.huffcdic + f), m = E(it, p); if (m.magic !== "CDIC") throw new Error("Invalid CDIC record"); const c = Math.min(1 << m.codeLength, m.numEntries - l.length), u = p.slice(m.length); for (let h = 0; h < c; h++) { const g = R(u.slice(h * 2, h * 2 + 2)), b = R(u.slice(g, g + 2)), w = b & 32767, y = b & 32768, A = new Uint8Array( u.slice(g + 2, g + 2 + w) ); l.push([A, y]); } } const d = (f) => { let p = new Uint8Array(); const m = f.byteLength * 8; for (let c = 0; c < m; ) { const u = Number(gt(f, c)); let [h, g, b] = i[u >>> 24]; if (!h) { for (; u >>> 32 - g < a[g][0]; ) g += 1; b = a[g][1]; } if ((c += g) > m) break; const w = b - (u >>> 32 - g); let [y, A] = l[w]; A || (y = d(y), l[w] = [y, !0]), p = _(p, y); } return p; }; return d; }, F = async (o, t) => { const e = await t(o), s = E(G, e); if (s.magic !== "INDX") throw new Error("Invalid INDX record"); const n = z(s.encoding), r = e.slice(s.length), i = E(nt, r); if (i.magic !== "TAGX") throw new Error("Invalid TAGX section"); const a = (i.length - 12) / 4, l = Array.from({ length: a }, (m, c) => new Uint8Array(r.slice(12 + c * 4, 12 + c * 4 + 4))), d = {}; let f = 0; for (let m = 0; m < s.numCncx; m++) { const c = await t(o + s.numRecords + m + 1), u = new Uint8Array(c); for (let h = 0; h < u.byteLength; ) { const g = h, { value: b, length: w } = H(u, h); h += w; const y = c.slice(h, h + b); h += b, d[f + g] = n.decode(y); } f += 65536; } const p = []; for (let m = 0; m < s.numRecords; m++) { const c = await t(o + 1 + m), u = new Uint8Array(c), h = E(G, c); if (h.magic !== "INDX") throw new Error("Invalid INDX record"); for (let g = 0; g < h.numRecords; g++) { const b = h.idxt + 4 + 2 * g, w = R(c.slice(b, b + 2)), y = R(c.slice(w, w + 1)), A = U(c.slice(w + 1, w + 1 + y)), S = [], M = w + 1 + y; let X = 0, I = M + i.numControlBytes; for (const [O, v, C, N] of l) { if (N & 1) { X++; continue; } const B = M + X, x = R(c.slice(B, B + 1)) & C; if (x === C) if (Z(C) > 1) { const { value: k, length: D } = H(u, I); S.push([O, null, k, v]), I += D; } else S.push([O, 1, null, v]); else S.push([O, x >> ht(C), null, v]); } const P = {}; for (const [O, v, C, N] of S) { const B = []; if (v != null) for (let x = 0; x < v * N; x++) { const { value: k, length: D } = H(u, I); B.push(k), I += D; } else { let x = 0; for (; x < C; ) { const { value: k, length: D } = H(u, I); B.push(k), I += D, x += D; } } P[O] = B; } p.push({ name: A, tagMap: P }); } } return { table: p, cncx: d }; }, pt = async (o, t) => { const { table: e, cncx: s } = await F(o, t), n = e.map(({ tagMap: i }, a) => { var l, d, f, p, m, c; return { index: a, offset: (l = i[1]) == null ? void 0 : l[0], size: (d = i[2]) == null ? void 0 : d[0], label: s[i[3]] ?? "", headingLevel: (f = i[4]) == null ? void 0 : f[0], pos: i[6], parent: (p = i[21]) == null ? void 0 : p[0], firstChild: (m = i[22]) == null ? void 0 : m[0], lastChild: (c = i[23]) == null ? void 0 : c[0] }; }), r = (i) => (i.firstChild == null || (i.children = n.filter((a) => a.parent === i.index).map(r)), i); return n.filter((i) => i.headingLevel === 0).map(r); }, bt = (o, t) => { const { magic: e, count: s } = E(st, o); if (e !== "EXTH") throw new Error("Invalid EXTH header"); const n = z(t), r = {}; let i = 12; for (let a = 0; a < s; a++) { const l = R(o.slice(i, i + 4)), d = R(o.slice(i + 4, i + 8)); if (l in j) { const [f, p, m] = j[l], c = o.slice(i + 8, i + d), u = p === "uint" ? R(c) : n.decode(c); m ? (r[f] ??= [], r[f].push(u)) : r[f] = u; } i += d; } return r; }, wt = async (o, t) => { const { flags: e, dataStart: s, keyLength: n, keyStart: r } = E(at, o), i = new Uint8Array(o.slice(s)); if (e & 2) { const l = n === 16 ? 1024 : 1040, d = new Uint8Array(o.slice(r, r + n)), f = Math.min(l, i.length); for (var a = 0; a < f; a++) i[a] = i[a] ^ d[a % d.length]; } if (e & 1) try { return await t(i); } catch (l) { console.warn(l), console.warn("Failed to decompress font"); } return i; }, vt = async (o) => U(await o.slice(60, 68).arrayBuffer()) === "BOOKMOBI"; class yt { #t; #e; pdb; async open(t) { this.#t = t; const e = E(J, await t.slice(0, 78).arrayBuffer()); this.pdb = e; const s = await t.slice(78, 78 + e.numRecords * 8).arrayBuffer(); this.#e = Array.from( { length: e.numRecords }, (n, r) => R(s.slice(r * 8, r * 8 + 4)) ).map((n, r, i) => [n, i[r + 1]]); } loadRecord(t) { const e = this.#e[t]; if (!e) throw new RangeError("Record index out of bounds"); return this.#t.slice(...e).arrayBuffer(); } async loadMagic(t) { const e = this.#e[t][0]; return U(await this.#t.slice(e, e + 4).arrayBuffer()); } } class Ct extends yt { #t = 0; #e; #n; #s; #r; #i; constructor({ unzlib: t }) { super(), this.unzlib = t; } async open(t) { var s; await super.open(t), this.headers = this.#o(await super.loadRecord(0)), this.#e = this.headers.mobi.resourceStart; let e = this.headers.mobi.version >= 8; if (!e) { const n = (s = this.headers.exth) == null ? void 0 : s.boundary; if (n < 4294967295) try { this.headers = this.#o(await super.loadRecord(n)), this.#t = n, e = !0; } catch (r) { console.warn(r), console.warn("Failed to open KF8; falling back to MOBI"); } } return await this.#a(), e ? new It(this).init() : new St(this).init(); } #o(t) { const e = E(Q, t), s = E(tt, t); if (s.magic !== "MOBI") throw new Error("Missing MOBI header"); const { titleOffset: n, titleLength: r, localeLanguage: i, localeRegion: a } = s; s.title = t.slice(n, n + r); const l = lt[i]; s.language = (l == null ? void 0 : l[a >> 2]) ?? (l == null ? void 0 : l[0]); const d = s.exthFlag & 64 ? bt(t.slice(s.length + 16), s.encoding) : null, f = s.version >= 8 ? E(et, t) : null; return { palmdoc: e, mobi: s, exth: d, kf8: f }; } async #a() { const { palmdoc: t, mobi: e } = this.headers; this.#n = z(e.encoding), this.#s = new TextEncoder(); const { compression: s } = t; if (this.#r = s === 1 ? (a) => a : s === 2 ? dt : s === 17480 ? await mt(e, this.loadRecord.bind(this)) : null, !this.#r) throw new Error("Unknown compression type"); const { trailingFlags: n } = e, r = n & 1, i = Z(n >>> 1); this.#i = (a) => { for (let l = 0; l < i; l++) { const d = ft(a); a = a.subarray(0, -d); } if (r) { const l = (a[a.length - 1] & 3) + 1; a = a.subarray(0, -l); } return a; }; } decode(...t) { return this.#n.decode(...t); } encode(...t) { return this.#s.encode(...t); } loadRecord(t) { return super.loadRecord(this.#t + t); } loadMagic(t) { return super.loadMagic(this.#t + t); } loadText(t) { return this.loadRecord(t + 1).then((e) => new Uint8Array(e)).then(this.#i).then(this.#r); } async loadResource(t) { const e = await super.loadRecord(this.#e + t), s = U(e.slice(0, 4)); return s === "FONT" ? wt(e, this.unzlib) : s === "VIDE" || s === "AUDI" ? e.slice(12) : e; } getNCX() { const t = this.headers.mobi.indx; if (t < 4294967295) return pt(t, this.loadRecord.bind(this)); } getMetadata() { var s, n; const { mobi: t, exth: e } = this.headers; return { identifier: t.uid.toString(), title: L((e == null ? void 0 : e.title) || this.decode(t.title)), author: (s = e == null ? void 0 : e.creator) == null ? void 0 : s.map(L), publisher: L(e == null ? void 0 : e.publisher), language: (e == null ? void 0 : e.language) ?? t.language, published: e == null ? void 0 : e.date, description: L(e == null ? void 0 : e.description), subject: (n = e == null ? void 0 : e.subject) == null ? void 0 : n.map(L), rights: L(e == null ? void 0 : e.rights), contributor: e == null ? void 0 : e.contributor }; } async getCover() { const { exth: t } = this.headers, e = (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 (e != null) { const s = await this.loadResource(e); return new Blob([s]); } } } const $ = /<\s*(?:mbp:)?pagebreak[^>]*>/gi, Rt = /<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>/gi, Et = (o) => { let t = 0; for (; o; ) { const e = o.parentElement; if (e) { const s = e.tagName.toLowerCase(); s === "p" ? t += 1.5 : s === "blockquote" && (t += 2); } o = e; } return t; }; class St { parser = new DOMParser(); serializer = new XMLSerializer(); #t = /* @__PURE__ */ new Map(); #e = /* @__PURE__ */ new Map(); #n = /* @__PURE__ */ new Map(); #s; #r = []; #i = T.HTML; constructor(t) { this.mobi = t; } async init() { var s; let t = new Uint8Array(); for (let n = 0; n < this.mobi.headers.palmdoc.numTextRecords; n++) t = _(t, await this.mobi.loadText(n)); const e = Array.from( new Uint8Array(t), (n) => String.fromCharCode(n) ).join(""); this.#s = [0].concat(Array.from(e.matchAll($), (n) => n.index)).map((n, r, i) => e.slice(n, i[r + 1])).map((n) => Uint8Array.from(n, (r) => r.charCodeAt(0))).map((n) => ({ book: this, raw: n })).reduce((n, r) => { const i = n[n.length - 1]; return r.start = (i == null ? void 0 : i.end) ?? 0, r.end = r.start + r.raw.byteLength, n.concat(r); }, []), this.sections = this.#s.map((n, r) => ({ id: r, 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: r }) => r == null ? void 0 : r.includes("toc"))) == null ? void 0 : s.href; if (n) { const { index: r } = this.resolveHref(n), i = await this.sections[r].createDocument(); let a, l = 0, d = 0; const f = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map(); this.toc = Array.from(i.querySelectorAll("a[filepos]")).reduce((m, c) => { var b; const u = Et(c), h = { label: ((b = c.innerText) == null ? void 0 : b.trim()) ?? "", href: `filepos:${c.getAttribute("filepos")}` }, g = u > d ? l + 1 : u === d ? l : f.get(u) ?? Math.max(0, l - 1); if (g > l) a ? (a.subitems ??= [], a.subitems.push(h), p.set(g, a)) : m.push(h); else { const w = p.get(g); w ? w.subitems.push(h) : m.push(h); } return a = h, l = g, d = u, f.set(u, g), m; }, []); } } catch (n) { console.warn(n); } return this.#r = [...new Set( Array.from(e.matchAll(Rt), (n) => n[1]) )].map((n) => ({ filepos: n, number: Number(n) })).sort((n, r) => n.number - r.number), this.metadata = this.mobi.getMetadata(), this.getCover = this.mobi.getCover.bind(this.mobi), this; } async getGuide() { const t = await this.createDocument(this.#s[0]); return Array.from(t.getElementsByTagName("reference"), (e) => { var s; return { label: e.getAttribute("title"), type: (s = e.getAttribute("type")) == null ? void 0 : s.split(/\s/), href: `filepos:${e.getAttribute("filepos")}` }; }); } async loadResource(t) { if (this.#t.has(t)) return this.#t.get(t); const e = await this.mobi.loadResource(t), s = URL.createObjectURL(new Blob([e])); return this.#t.set(t, s), s; } async loadRecindex(t) { return this.loadResource(Number(t) - 1); } async replaceResources(t) { for (const e of t.querySelectorAll("img[recindex]")) { const s = e.getAttribute("recindex"); try { e.src = await this.loadRecindex(s); } catch { console.warn(`Failed to load image ${s}`); } } for (const e of t.querySelectorAll("[mediarecindex]")) { const s = e.getAttribute("mediarecindex"), n = e.getAttribute("recindex"); try { e.src = await this.loadRecindex(s), n && (e.poster = await this.loadRecindex(n)); } catch { console.warn(`Failed to load media ${s}`); } } for (const e of t.querySelectorAll("[filepos]")) { const s = e.getAttribute("filepos"); e.href = `filepos:${s}`; } } async loadText(t) { if (this.#e.has(t)) return this.#e.get(t); const { raw: e } = t, s = this.#r.filter(({ number: i }) => i >= t.start && i < t.end).map((i) => ({ ...i, offset: i.number - t.start })); let n = e; s.length && (n = e.subarray(0, s[0].offset), s.forEach(({ filepos: i, offset: a }, l) => { const d = s[l + 1], f = this.mobi.encode(`<a id="filepos${i}"></a>`); n = Y(n, f, e.subarray(a, d == null ? void 0 : d.offset)); })); const r = this.mobi.decode(n).replaceAll($, ""); return this.#e.set(t, r), r; } async createDocument(t) { const e = await this.loadText(t); return this.parser.parseFromString(e, this.#i); } async loadSection(t) { if (this.#n.has(t)) return this.#n.get(t); const e = await this.createDocument(t), s = e.createElement("style"); e.head.append(s), s.append(e.createTextNode(`blockquote { margin-block-start: 0; margin-block-end: 0; margin-inline-start: 1em; margin-inline-end: 0; }`)), await this.replaceResources(e); const n = this.serializer.serializeToString(e), r = URL.createObjectURL(new Blob([n], { type: this.#i })); return this.#n.set(t, r), r; } resolveHref(t) { const e = t.match(/filepos:(.*)/)[1], s = Number(e); return { index: this.#s.findIndex((i) => i.end > s), anchor: (i) => i.getElementById(`filepos${e}`) }; } splitTOCHref(t) { const e = t.match(/filepos:(.*)/)[1], s = Number(e); return [this.#s.findIndex((r) => r.end > s), `filepos${e}`]; } getTOCFragment(t, e) { return t.getElementById(e); } isExternal(t) { return /^(?!blob|filepos)\w+:/i.test(t); } destroy() { for (const t of this.#t.values()) URL.revokeObjectURL(t); for (const t of this.#n.values()) URL.revokeObjectURL(t); } } const W = /kindle:(flow|embed):(\w+)(?:\?mime=(\w+\/[-+.\w]+))?/, At = /kindle:pos:fid:(\w+):off:(\w+)/, Tt = (o) => { const [t, e, s] = o.match(W).slice(1); return { resourceType: t, id: parseInt(e, 32), type: s }; }, q = (o) => { const [t, e] = o.match(At).slice(1); return { fid: parseInt(t, 32), off: parseInt(e, 32) }; }, V = (o = 0, t = 0) => `kindle:pos:fid:${o.toString(32).toUpperCase().padStart(4, "0")}:off:${t.toString(32).toUpperCase().padStart(10, "0")}`, K = (o) => { const t = o.match(/\s(id|name|aid)\s*=\s*['"]([^'"]*)['"]/i); if (!t) return; const [, e, s] = t; return `[${e}="${CSS.escape(s)}"]`; }, xt = async (o, t, e) => { const s = []; o.replace(t, (...r) => (s.push(r), null)); const n = []; for (const r of s) n.push(await e(...r)); return o.replace(t, () => n.shift()); }, Lt = (o) => { for (const t of o) { if (t === "page-spread-left" || t === "rendition:page-spread-left") return "left"; if (t === "page-spread-right" || t === "rendition:page-spread-right") return "right"; if (t === "rendition:page-spread-center") return "center"; } }; class It { parser = new DOMParser(); serializer = new XMLSerializer(); #t = /* @__PURE__ */ new Map(); #e = /* @__PURE__ */ new Map(); #n = /* @__PURE__ */ new Map(); #s = {}; #r; #i; #o = new Uint8Array(); #a = new Uint8Array(); #f = -1; #h = -1; #c = T.XHTML; #l = /* @__PURE__ */ new Map(); constructor(t) { this.mobi = t; } async init() { var d, f, p, m; const t = this.mobi.loadRecord.bind(this.mobi), { kf8: e } = this.mobi.headers; try { const c = await t(e.fdst), u = E(ot, c); if (u.magic !== "FDST") throw new Error("Missing FDST record"); const h = Array.from( { length: u.numEntries }, (g, b) => 12 + b * 8 ).map((g) => [ R(c.slice(g, g + 4)), R(c.slice(g + 4, g + 8)) ]); this.#s.fdstTable = h, this.#i = h[h.length - 1][1]; } catch { } const s = (await F(e.skel, t)).table.map(({ name: c, tagMap: u }, h) => ({ index: h, name: c, numFrag: u[1][0], offset: u[6][0], length: u[6][1] })), n = await F(e.frag, t), r = n.table.map(({ name: c, tagMap: u }) => ({ insertOffset: parseInt(c), selector: n.cncx[u[2][0]], index: u[4][0], offset: u[6][0], length: u[6][1] })); this.#s.skelTable = s, this.#s.fragTable = r, this.#r = s.reduce((c, u) => { const h = c[c.length - 1], g = (h == null ? void 0 : h.fragEnd) ?? 0, b = g + u.numFrag, w = r.slice(g, b), y = u.length + w.map((S) => S.length).reduce((S, M) => S + M), A = ((h == null ? void 0 : h.totalLength) ?? 0) + y; return c.concat({ skel: u, frags: w, fragEnd: b, length: y, totalLength: A }); }, []); const i = await this.getResourcesByMagic(["RESC", "PAGE"]), a = /* @__PURE__ */ new Map(); if (i.RESC) { const c = await this.mobi.loadRecord(i.RESC), u = this.mobi.decode(c.slice(16)).replace(/\0/g, ""), h = u.search(/\?>/), g = `<package>${u.slice(h)}</package>`, b = this.parser.parseFromString(g, T.XML); for (const w of b.querySelectorAll("spine > itemref")) { const y = parseInt(w.getAttribute("skelid")); a.set(y, Lt( ((d = w.getAttribute("properties")) == null ? void 0 : d.split(" ")) ?? [] )); } } this.sections = this.#r.map((c, u) => c.frags.length ? { id: u, load: () => this.loadSection(c), createDocument: () => this.createDocument(c), size: c.length, pageSpread: a.get(u) } : { linear: "no" }); try { const c = await this.mobi.getNCX(), u = ({ label: h, pos: g, children: b }) => { const [w, y] = g, A = V(w, y), S = this.#e.get(w); return S ? S.push(y) : this.#e.set(w, [y]), { label: L(h), href: A, subitems: b == null ? void 0 : b.map(u) }; }; this.toc = c == null ? void 0 : c.map(u), this.landmarks = await this.getGuide(); } catch (c) { console.warn(c); } const { exth: l } = this.mobi.headers; return this.dir = l.pageProgressionDirection, this.rendition = { layout: l.fixedLayout === "true" ? "pre-paginated" : "reflowable", viewport: Object.fromEntries(((m = (p = (f = l.originalResolution) == null ? void 0 : f.split("x")) == null ? void 0 : p.slice(0, 2)) == null ? void 0 : m.map((c, u) => [u ? "height" : "width", c])) ?? []) }, 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(t) { const e = {}, s = this.mobi.headers.kf8.resourceStart, n = this.mobi.pdb.numRecords; for (let r = s; r < n; r++) try { const i = await this.mobi.loadMagic(r), a = t.find((l) => l === i); a && (e[a] = r); } catch { } return e; } async getGuide() { const t = this.mobi.headers.kf8.guide; if (t < 4294967295) { const e = this.mobi.loadRecord.bind(this.mobi), { table: s, cncx: n } = await F(t, e); return s.map(({ name: r, tagMap: i }) => { var a, l; return { label: n[i[1][0]] ?? "", type: r == null ? void 0 : r.split(/\s/), href: V(((a = i[6]) == null ? void 0 : a[0]) ?? ((l = i[3]) == null ? void 0 : l[0])) }; }); } } async loadResourceBlob(t) { var l; const { resourceType: e, id: s, type: n } = Tt(t), r = e === "flow" ? await this.loadFlow(s) : await this.mobi.loadResource(s - 1), i = [T.XHTML, T.HTML, T.CSS, T.SVG].includes(n) ? await this.replaceResources(this.mobi.decode(r)) : r, a = n === T.SVG ? this.parser.parseFromString(i, n) : null; return [ new Blob([i], { type: n }), // SVG wrappers need to be inlined // as browsers don't allow external resources when loading SVG as an image (l = a == null ? void 0 : a.getElementsByTagNameNS("http://www.w3.org/2000/svg", "image")) != null && l.length ? a.documentElement : null ]; } async loadResource(t) { if (this.#t.has(t)) return this.#t.get(t); const [e, s] = await this.loadResourceBlob(t), n = s ? t : URL.createObjectURL(e); return s && this.#l.set(n, s), this.#t.set(t, n), n; } replaceResources(t) { const e = new RegExp(W, "g"); return xt(t, e, 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(t, e) { const s = e - this.#o.length, n = this.#i == null ? 1 / 0 : this.#i - this.#a.length - t; if (s < 0 || s < n) { for (; this.#o.length < e; ) { const i = ++this.#f, a = await this.mobi.loadText(i); this.#o = _(this.#o, a); } return this.#o.slice(t, e); } for (; this.#i - this.#a.length > t; ) { const i = this.mobi.headers.palmdoc.numTextRecords - 1 - ++this.#h, a = await this.mobi.loadText(i); this.#a = _(a, this.#a); } const r = this.#i - this.#a.length; return this.#a.slice(t - r, e - r); } loadFlow(t) { if (t < 4294967295) return this.loadRaw(...this.#s.fdstTable[t]); } async loadText(t) { const { skel: e, frags: s, length: n } = t, r = await this.loadRaw(e.offset, e.offset + n); let i = r.slice(0, e.length); for (const a of s) { const l = a.insertOffset - e.offset, d = e.length + a.offset, f = r.slice(d, d + a.length); i = Y( i.slice(0, l), f, i.slice(l) ); const p = this.#e.get(a.index); if (p) for (const m of p) { const c = this.mobi.decode(f).slice(m), u = K(c); this.#u(a.index, m, u); } } return this.mobi.decode(i); } async createDocument(t) { const e = await this.loadText(t); return this.parser.parseFromString(e, this.#c); } async loadSection(t) { var i; if (this.#t.has(t)) return this.#t.get(t); const e = await this.loadText(t), s = await this.replaceResources(e); let n = this.parser.parseFromString(s, this.#c); (n.querySelector("parsererror") || !((i = n.documentElement) != null && i.namespaceURI)) && (this.#c = T.HTML, n = this.parser.parseFromString(s, this.#c)); for (const [a, l] of this.#l) for (const d of n.querySelectorAll(`img[src="${a}"]`)) d.replaceWith(l); const r = URL.createObjectURL( new Blob([this.serializer.serializeToString(n)], { type: this.#c }) ); return this.#t.set(t, r), r; } getIndexByFID(t) { return this.#r.findIndex((e) => e.frags.some((s) => s.index === t)); } #u(t, e, s) { const n = this.#n.get(t); if (n) n.set(e, s); else { const r = /* @__PURE__ */ new Map(); this.#n.set(t, r), r.set(e, s); } } async resolveHref(t) { var u; const { fid: e, off: s } = q(t), n = this.getIndexByFID(e); if (n < 0) return; const r = (u = this.#n.get(e)) == null ? void 0 : u.get(s); if (r) return { index: n, anchor: (h) => h.querySelector(r) }; const { skel: i, frags: a } = this.#r[n], l = a.find((h) => h.index === e), d = i.offset + i.length + l.offset, f = await this.loadRaw(d, d + l.length), p = this.mobi.decode(f).slice(s), m = K(p); return this.#u(e, s, m), { index: n, anchor: (h) => h.querySelector(m) }; } splitTOCHref(t) { const e = q(t); return [this.getIndexByFID(e.fid), e]; } getTOCFragment(t, { fid: e, off: s }) { var r; const n = (r = this.#n.get(e)) == null ? void 0 : r.get(s); return t.querySelector(n); } isExternal(t) { return /^(?!blob|kindle)\w+:/i.test(t); } destroy() { for (const t of this.#t.values()) URL.revokeObjectURL(t); } } export { Ct as MOBI, vt as isMOBI };