Files
catena/crates/pdfgutter/README.md
T

165 lines
5.2 KiB
Markdown
Raw Normal View History

# pdfgutter
Finds books scanned two pages to a sheet, and splits them back into pages.
Part of [catena](../../README.md).
## What it does
A book held open under a camera gives one wide image per shot, with two pages
on it. Everything that reads it afterwards runs the two columns together: the
lines interleave, the reading order comes out scrambled, and what lands in the
index looks like prose and is not.
`pdfgutter` says whether a PDF is that, and splits it when it is. The split
changes the page boxes and nothing else — no re-rendering, no loss, and a
250-sheet book becomes a 500-page book at almost no cost in size.
## When you would use it
- **Before OCR.** Layout models read a spread as one page and produce jumbled
text, or none.
- **Before chunking.** A passage that alternates lines between two pages is
unusable and does not look it.
- **Sorting a scanned library.** Most books are fine; the answer tells you
which ones are not.
## Install
```sh
cargo install --git https://git.pumpin.online/funkpower/catena pdfgutter
```
Detection needs `pdftoppm`, from poppler. Splitting needs nothing.
## Example
A book photographed two pages at a time:
```
$ pdfgutter scan.pdf
two pages to a sheet
12 pages, median ratio 1.40, gutter on 100% of the wide ones
```
An ordinary one:
```
$ pdfgutter book.pdf
one page to a sheet
12 pages, median ratio 0.68, gutter on 0% of the wide ones
$ echo $?
1
```
Splitting it:
```
$ pdfgutter --into split.pdf scan.pdf
two pages to a sheet
12 pages, median ratio 1.40, gutter on 100% of the wide ones
20 pages written to split.pdf
```
Twelve sheets became twenty pages: eight spreads split in two, and the four
covers and end leaves, which were single pages already, left alone.
What it looked at:
```
$ pdfgutter --json scan.pdf | jq -c '.sampled[:3][]'
{"number":3,"ratio":1.4,"gutter":true}
{"number":4,"ratio":1.4,"gutter":true}
{"number":5,"ratio":1.4,"gutter":true}
```
Over a directory, splitting only what needs it:
```sh
for f in *.pdf; do
pdfgutter --quiet "$f" && pdfgutter --quiet --into "split/$f" "$f" || cp "$f" "split/$f"
done
```
## Options
| Option | Effect |
|---|---|
| `--into <FILE>` | Write the split document here |
| `--json` | Emit a record — every page looked at, and what it measured |
| `--quiet`, `-q` | Say nothing; the exit code is the answer |
| `--min-ratio <RATIO>` | How wide a sheet must be to hold two pages. Default `1.15` |
| `--gutter-fraction <FRACTION>` | What share of wide sheets must show a gutter. Default `0.6` |
`--into` writes whatever the verdict was: a document that is not two-up is
copied through unchanged, so a loop over a directory does not have to branch.
## Exit codes
| Code | Meaning |
|---|---|
| `0` | Two pages to a sheet |
| `1` | One page to a sheet |
| `2` | It could not be judged — encrypted, or no pages |
| `64` | The command line was wrong |
| `69` | `pdftoppm` is not installed |
| `74` | The file could not be read or written |
## How it decides
**Shape is not enough.** A foldout, a plate, a page set in one narrow centred
column are all genuinely wide, and splitting one down the middle destroys it.
So the shape of the ink is checked as well.
Up to eight pages are sampled, evenly spaced and away from the ends — covers
and end matter are single pages even in a book otherwise shot two at a time,
and sampling them would argue against a document that plainly is a spread.
Each sampled page that is wide enough to hold two pages gets rendered small
and read as a column-by-column measure of how much ink is on it. A gutter is:
- a **blank column** somewhere in the middle eight per cent of the width,
- with **real text** in the third of the page to its left,
- and **real text** in the third to its right.
The outer tenth of the sheet is ignored on both sides, because that is where
a scan carries the shadow of the binding and of the table the book sat on.
A document is two-up when the **middle** sampled page is wide enough and
**at least three in five** of the wide ones showed a gutter. The middle rather
than the average throughout, so that one plate or one blank leaf does not move
the answer for a whole book.
### The split
Each wide sheet becomes two pages pointing at the same content, differing only
in how much of it they show. A sheet the file asks to be turned on its side is
cut across rather than down, and the halves come out in the order a reader
would read them.
Splitting is safe to repeat: the halves are portrait, so a second pass finds
nothing to do.
## As a library
```rust
let mut pdf = pdfgutter::pdf::Pdf::open(std::path::Path::new("scan.pdf"))?;
for index in pdfgutter::sample_indices(pdf.len()) {
println!("page {} ratio {:.2}", index + 1, pdf.geometry(index).ratio());
}
let pages = pdf.split(pdfgutter::MIN_RATIO);
pdf.save(std::path::Path::new("split.pdf"))?;
println!("{pages} pages");
# Ok::<(), pdfgutter::pdf::Error>(())
```
`pdfgutter::has_gutter` takes a column ink profile and answers on its own, for
anything that rasterises its own pages. `pdfgutter::ink::profile` turns a
greymap into one.
## See also
- [`pdftriage`](../pdftriage/README.md) — whether the text layer is worth
keeping, which is the next question after this one.
- [`booksniff`](../booksniff/README.md) — whether the file is a PDF at all.