mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-10-22 06:49:51 -04:00
feat(rtff-pdb): RTF and PalmDOC plain-text support (v0.5.0)
rtf.js: a minimal single-pass RTF tokenizer extracting document text
with paragraph structure — groups, control words with signed params,
\\'hh Windows-1252 escapes, \\uN unicode with \\ucN fallback skipping,
known text destinations skipped whole (fonttbl/colortbl/stylesheet/
info/headers/pict/field/...), spec-required ignoring of unknown words.
Format runs are intentionally not converted; RTF is read as styled
text like .txt.
pdb.js: the TEXt/REAd PalmDOC container (PalmDOC, TealDoc) — the PDB
record reader and LZ77 decompressor are shared from mobi.js (both
exported; upstream already carried the TODO for this path). Records
decode as Windows-1252 and feed the plain-text pipeline (paragraph
split, line join, ~150KB sectioning, Part N TOC).
makeBook dispatch: content-based (TEXtREAd magic at offset 60;
'{\\rtf' magic), after MOBI and before the text branch.
Tests: node harness covering store + compressed PalmDOC, RTF paragraphs
/escapes/unicode/hex/destination-skipping, makeBook routing; txt/docx
regression suites pass.
This commit is contained in:
1 parent
50f56ee67f
commit
ac699e71ee
5 files changed
+218
-4
No files matched your search
@@ -0,0 +1,57 @@
|
||||
// Plain-text Palm database (.pdb) support: the "TEXt"/"REAd" PDB
|
||||
// container (PalmDOC, also TealDoc/TealPaint-text databases) stores a
|
||||
// text document as numbered records, each optionally compressed with
|
||||
// the PalmDOC LZ77 variant (the same compression MOBI uses — the
|
||||
// container machinery is shared with mobi.js). Records are decoded as
|
||||
// Windows-1252 (Palm-era text predates UTF-8) and the resulting text is
|
||||
// handed to the plain-text pipeline for paragraph splitting, joining,
|
||||
// and sectioning.
|
||||
//
|
||||
// Limitations (by design, MVP): no HUFF/CDIC compression (record 0
|
||||
// compression type 17480 — that is MOBI territory, handled by mobi.js),
|
||||
// no TealDoc anchor/link extensions, Windows-1252 decoding only.
|
||||
|
||||
import { getString } from './mobi.js'
|
||||
import { makeTextBook } from './text.js'
|
||||
|
||||
const PDB_MAGIC_OFFSET = 60 // type (4) + creator (4)
|
||||
|
||||
export const isPDB = async file => {
|
||||
const magic = getString(await file.slice(PDB_MAGIC_OFFSET, PDB_MAGIC_OFFSET + 8).arrayBuffer())
|
||||
return magic === 'TEXtREAd'
|
||||
}
|
||||
|
||||
export const makePalmDocBook = async file => {
|
||||
const { PDB, decompressPalmDOC } = await import('./mobi.js')
|
||||
const pdb = new PDB()
|
||||
await pdb.open(file)
|
||||
// record 0 opens with the PalmDOC header: compression (2 bytes),
|
||||
// unused (2), text length (4), num text records (2), record size (2),
|
||||
// encryption (2) — followed by optional TealDoc data
|
||||
const r0 = new Uint8Array(await pdb.loadRecord(0))
|
||||
const compression = (r0[0] << 8) | r0[1]
|
||||
const numTextRecords = (r0[8] << 8) | r0[9]
|
||||
|
||||
if (compression !== 1 && compression !== 2)
|
||||
throw new Error(`Unsupported PalmDOC compression type ${compression}`)
|
||||
const count = numTextRecords || pdb.pdb.numRecords - 1
|
||||
|
||||
const parts = []
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const buf = new Uint8Array(await pdb.loadRecord(i))
|
||||
parts.push(compression === 2 ? decompressPalmDOC(buf) : buf)
|
||||
}
|
||||
const total = parts.reduce((sum, p) => sum + p.length, 0)
|
||||
const bytes = new Uint8Array(total)
|
||||
for (let i = 0, offset = 0; i < parts.length; i++) {
|
||||
bytes.set(parts[i], offset)
|
||||
offset += parts[i].length
|
||||
}
|
||||
// Palm-era text is Windows-1252 (Latin-1 superset), not UTF-8
|
||||
const text = new TextDecoder('windows-1252').decode(bytes)
|
||||
const name = (pdb.pdb.name || '').replace(/\0/g, '').trim()
|
||||
const fileName = name || file.name.replace(/\.[^.]+$/, '')
|
||||
|
||||
// reuse the plain-text pipeline: paragraph split, line join, sections
|
||||
return makeTextBook(new File([text], `${fileName}.txt`, { type: 'text/plain' }))
|
||||
}
|
||||
Reference in new issue
Block a user