@triabin/vue-book-reader
Version:
Forked from jinhuan138/vue-book-reader, add some features
969 lines (968 loc) • 31.8 kB
JavaScript
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
};