Speed up text sectioning for large MOBI books (#92)

This commit is contained in:
Huang Xin
2025-08-01 23:19:23 +00:00
committed by GitHub
parent f1d4a4290d
commit 55c0027d11
+30 -16
View File
@@ -658,6 +658,15 @@ const getIndent = el => {
return x return x
} }
function rawBytesToString(uint8Array) {
const chunkSize = 0x8000
let result = ''
for (let i = 0; i < uint8Array.length; i += chunkSize) {
result += String.fromCharCode.apply(null, uint8Array.subarray(i, i + chunkSize))
}
return result
}
class MOBI6 { class MOBI6 {
parser = new DOMParser() parser = new DOMParser()
serializer = new XMLSerializer() serializer = new XMLSerializer()
@@ -671,32 +680,37 @@ class MOBI6 {
this.mobi = mobi this.mobi = mobi
} }
async init() { async init() {
const recordBuffers = []
for (let i = 0; i < this.mobi.headers.palmdoc.numTextRecords; i++) {
const buf = await this.mobi.loadText(i)
recordBuffers.push(buf)
}
const totalLength = recordBuffers.reduce((sum, buf) => sum + buf.byteLength, 0)
// load all text records in an array // load all text records in an array
let array = new Uint8Array() const array = new Uint8Array(totalLength)
for (let i = 0; i < this.mobi.headers.palmdoc.numTextRecords; i++) recordBuffers.reduce((offset, buf) => {
array = concatTypedArray(array, await this.mobi.loadText(i)) array.set(new Uint8Array(buf), offset)
return offset + buf.byteLength
}, 0)
// convert to string so we can use regex // convert to string so we can use regex
// note that `filepos` are byte offsets // note that `filepos` are byte offsets
// so it needs to preserve each byte as a separate character // so it needs to preserve each byte as a separate character
// (see https://stackoverflow.com/q/50198017) // (see https://stackoverflow.com/q/50198017)
const str = Array.from(new Uint8Array(array), const str = rawBytesToString(array)
c => String.fromCharCode(c)).join('')
// split content into sections at each `<mbp:pagebreak>` // split content into sections at each `<mbp:pagebreak>`
this.#sections = [0] this.#sections = [0]
.concat(Array.from(str.matchAll(mbpPagebreakRegex), m => m.index)) .concat(Array.from(str.matchAll(mbpPagebreakRegex), m => m.index))
.map((x, i, a) => str.slice(x, a[i + 1])) .map((start, i, a) => {
// recover the original raw bytes const end = a[i + 1] ?? array.length
.map(str => Uint8Array.from(str, x => x.charCodeAt(0))) return { book: this, raw: array.subarray(start, end) }
.map(raw => ({ book: this, raw })) })
// get start and end filepos for each section // get start and end filepos for each section
.reduce((arr, x) => { .map((section, i, arr) => {
const last = arr[arr.length - 1] section.start = arr[i - 1]?.end ?? 0
x.start = last?.end ?? 0 section.end = section.start + section.raw.byteLength
x.end = x.start + x.raw.byteLength return section
return arr.concat(x) })
}, [])
this.sections = this.#sections.map((section, index) => ({ this.sections = this.#sections.map((section, index) => ({
id: index, id: index,