# Merging and splitting

Merging combines several PDF files into one, such as a cover and the chapters
that follow it. Splitting cuts one document into parts, such as a report into
one file per section. Both work on an open `PdfElixide.Editor`: a merge brings
pages into the editor and changes it, and a split writes pages out of it as new
binaries and leaves it alone. Either way, the editor's pending edits travel with
its pages.

## Choosing the right call

Three calls keep only some of a document's pages, and they differ in what they
change and what they leave behind:

| | `PdfElixide.Editor.keep_pages/2` | `PdfElixide.Editor.extract_pages/2` | `PdfElixide.Editor.delete_page/2` |
|---|---|---|---|
| Changes the editor | yes | no | yes |
| Result | the editor, written later | a new PDF binary | the editor, written later |
| Order of the kept pages | as listed | as listed | unchanged |
| Content of the pages left out | stays in the written file, unreferenced | left out, unless something still refers to it | stays in the written file, unreferenced |

`PdfElixide.Editor.keep_pages/2` and `PdfElixide.Editor.delete_page/2` rearrange
the open document and are covered under
[Page structure](editing.md#page-structure). None of the three is a reliable way
to remove content; see
[Extraction is not redaction](#extraction-is-not-redaction).

## Merging documents

`PdfElixide.Editor.merge/2` appends every page of a PDF file to the end of the
editor, and `PdfElixide.Editor.merge_binary/2` does the same for a PDF binary:

```elixir
alias PdfElixide.Document
alias PdfElixide.Editor

"path/to/cover.pdf"
|> Editor.open!()
|> Editor.merge!("path/to/chapter_1.pdf")
|> Editor.merge!("path/to/chapter_2.pdf")
|> Editor.save!("book.pdf")
|> Editor.close()
#=> :ok
```

A merge incorporates the editor's pending edits into the combined document.
Afterwards, the editor behaves as if it had been opened on a file that already
held every page:

  * Merged pages are ordinary pages: `PdfElixide.Editor.delete_page/2`,
    `PdfElixide.Editor.move_page/3`, rotation and page boxes work on them as on
    any other.
  * Pending edits become part of the document. Flatten marks and erased regions
    are drawn, and their warnings stay available from
    `PdfElixide.Editor.flatten_warnings/1`. Pending redactions are refused
    instead; see below.
  * Attachments become part of the document, so `PdfElixide.Editor.embed_file/4`
    is refused from then on; see [Attachments](editing.md#attachments).
  * `PdfElixide.Editor.modified?/1` reports `true` until the next full write,
    and an incremental save is refused.

Repeated merges become more expensive as the combined document grows.

### What a merge keeps

Merged pages keep their content, rotation, media box and crop box. A page that
had no crop box may report an explicit crop box equal to its media box after
merging. What belongs to the merged document as a whole is not carried, such as
its form fields and signatures, bookmarks, named destinations, page labels,
structure tree, attachments and document information. A resource that several of
its pages share, such as an embedded font, is written once for each page that
uses it, so the result can be larger than the two documents together.

The combined document keeps this editor's declared PDF version, even when the
merged document declares a later one; see
[The declared version is not raised to match](encryption.md#the-declared-version-is-not-raised-to-match).

### When a merge is refused

Some documents are refused with
`{:error, %PdfElixide.Error{reason: :unsupported}}`, and the editor is left as
it was:

  * **A page with annotations.** Links, comments and form fields refer to other
    pages or to the document's form, and a merge would leave them pointing
    outside the result. Flattening them in that document first with
    `PdfElixide.Editor.flatten_annotations/1` and `PdfElixide.Form.flatten/1`,
    then merging the written result, usually removes them; a page where any
    remain is refused again.
  * **A page that refers to other pages in any other way**, such as an article
    thread that continues on another page, a page action that goes to one (by
    page or by a named destination) or to an article thread, a color separation
    group or presentation steps. The error names what the page holds.
  * **A page action that runs JavaScript or acts on form fields or
    attachments**, such as one that resets, submits or imports form data, hides
    a field by name, or opens an attachment of that document. Once merged it
    would act on this document's fields and attachments instead.
    `PdfElixide.Editor.sanitize/2` removes only document-level JavaScript, so it
    cannot prepare such a page for merging.
  * **A page that uses layers (optional content).** The merged document's layer
    settings are not carried, so a hidden layer would show and the list of
    layers would be lost.
  * **A page that takes its fonts, images or other resources from its page
    tree** rather than declaring them itself. The merged page would lose them.
  * **A page that declares no resources at all, when this document's page tree
    declares some.** The merged page would take this document's fonts and images
    as its own. Such a page merges into a document whose page tree declares
    none.
  * **A tagged page, when this document is tagged too.** The page's structure
    entries would attach its content to this document's structure elements, so
    assistive technology would read it in the wrong place. A tagged page merges
    into an untagged document, where its structure is not carried.
  * **A page with an excessively nested object graph.**
  * **An editor with a pending redaction**: a region queued with
    `PdfElixide.Editor.add_redaction/3`, or a page marked with
    `PdfElixide.Editor.mark_redactions/2` that has redaction annotations. The
    merge would lose it, and a later `PdfElixide.Editor.apply_redactions/1`
    would remove nothing. Apply it first, or withdraw a mark with
    `PdfElixide.Editor.unmark_redactions/2`. A region queued after
    `PdfElixide.Editor.apply_redactions/1` has run cannot be applied in that
    editor at all: write it with `PdfElixide.Editor.to_binary/2`, open the
    result, and redact it there before merging. An editor whose
    `PdfElixide.Editor.apply_redactions/1` failed is refused too: discard it and
    reopen the source.
  * **An editor with no pages.** Merge before deleting the last page.
  * **An editor converted to PDF/A-1** with `PdfElixide.Compliance.convert/3`.
    The merge would compress the document's XMP metadata, which PDF/A-1 forbids.
    Merge first, then convert; see the [PDF/A conversion](pdf-a-conversion.md)
    guide.

Merging an unencrypted document with no pages changes nothing and succeeds.

An encrypted document returns `{:error, %PdfElixide.Error{reason: :encrypted}}`,
even one that opens without a password. Open it as an editor, with its password
if it needs one, write it with `PdfElixide.Editor.to_binary/2`, and merge the
result with `PdfElixide.Editor.merge_binary/2`.

### Taking only some pages from another document

A merge takes every page of the other document. To take only some, extract them
from that document first and merge the result:

```elixir
source = Editor.open!("path/to/appendix.pdf")
second_page = Editor.extract_pages!(source, [1])
Editor.close(source)

"path/to/report.pdf"
|> Editor.open!()
|> Editor.merge_binary!(second_page)
|> Editor.save!("report-with-appendix.pdf")
|> Editor.close()
#=> :ok
```

The extracted pages carry the other editor's pending edits, and the merge
refuses them for the same reasons it would refuse the whole document.

## Splitting a document

`PdfElixide.Editor.extract_pages/2` writes the listed pages, in the order given,
to a new PDF binary without changing the editor's pages or pending edits.
`File.write!/2` saves one:

```elixir
editor = Editor.open!("path/to/report.pdf")

File.write!("summary.pdf", Editor.extract_pages!(editor, [0, 1]))
```

The examples below reuse this `editor` until the next one opens its own.

Extracted documents include pending edits but are not encrypted. Open one with
`PdfElixide.Editor.from_binary/2` and write it with `:encryption` if it needs a
password. An empty list or a repeated index raises `ArgumentError`, and a page
the document cannot resolve returns
`{:error, %PdfElixide.Error{reason: :invalid_pdf}}` rather than being left out.

### Parts and chunks

`PdfElixide.Editor.extract_page_ranges/2` does the same for each of several
inclusive ranges, and returns the binaries in the order of the ranges. Ranges
may overlap:

```elixir
[front, back] = Editor.extract_page_ranges!(editor, [0..9, 10..19])
```

To cut a document into parts of a fixed size, build the ranges from the page
count:

```elixir
chunks =
  0..(Editor.page_count!(editor) - 1)//1
  |> Enum.chunk_every(10)
  |> Enum.map(fn pages -> List.first(pages)..List.last(pages) end)

editor
|> Editor.extract_page_ranges!(chunks)
|> Enum.with_index(1)
|> Enum.each(fn {bytes, n} -> File.write!("part-#{n}.pdf", bytes) end)

:ok = Editor.close(editor)
```

All the binaries from one call are in memory together. To hold only one at a
time, call `PdfElixide.Editor.extract_pages/2` once per part.

### Splitting at bookmarks

`PdfElixide.Editor.split_by_bookmarks/2` cuts the document where its bookmarks
point, so a book with one bookmark per chapter becomes one binary per chapter.
It returns each part with the `PdfElixide.Document.BookmarkSegment` describing
it:

```elixir
editor = Editor.open!("path/to/book.pdf")

editor
|> Editor.split_by_bookmarks!()
|> Enum.each(fn {segment, bytes} -> File.write!("#{segment.file_stem}.pdf", bytes) end)
```

A part runs from its bookmark's page to the page before the next bookmark's; the
last runs to the end. Parts include pending edits and document information, and
are not encrypted.

Every part also keeps the whole document's bookmarks, and with them every page a
bookmark points to, including the first page of each other part that starts at a
bookmark. Those bookmarks lead to pages the part does not show. That adds to
each part's size, and anyone reading a part's bytes can recover those pages; see
[Extraction is not redaction](#extraction-is-not-redaction).

Plan the parts without writing them with
`PdfElixide.Document.bookmark_segments/2` on a read-only document, or
`PdfElixide.Editor.bookmark_segments/2` on an editor. By default each top-level
bookmark starts a part, and the pages before the first become a part of their
own, with a `nil` title and the stem `"front-matter"`. Options choose deeper
bookmarks, filter them by title, or leave the front matter out; see
`t:PdfElixide.Document.bookmark_opts/0`:

```elixir
book = Document.open!("path/to/book.pdf")
segments = Document.bookmark_segments!(book, depth: 2, title_prefix: "Chapter")
:ok = Document.close(book)
```

Bookmarks that do not point to a page are skipped. When several point to the
same page, the first starts the part. No matching bookmarks returns `{:ok, []}`;
use [Parts and chunks](#parts-and-chunks) instead.

On an editor, the parts follow the pages the editor would write now. After
`PdfElixide.Editor.move_page/3`, `PdfElixide.Editor.delete_page/2` or
`PdfElixide.Editor.keep_pages/2`, each bookmark follows its page to its new
position, and a bookmark whose page is gone is skipped.
`PdfElixide.Document.bookmark_segments/2` always plans the pages as they are in
the file.

Every part is in memory until `PdfElixide.Editor.split_by_bookmarks/2` returns.
To hold one at a time, plan on the editor and extract each part yourself:

```elixir
for segment <- Editor.bookmark_segments!(editor) do
  [bytes] = Editor.extract_page_ranges!(editor, [segment.pages])
  File.write!("#{segment.file_stem}.pdf", bytes)
end

:ok = Editor.close(editor)
```

### Extraction is not redaction

`PdfElixide.Editor.extract_pages/2` leaves out the pages you did not list, but
only when nothing else in the document still refers to them. Anything that does
— a bookmark, link or named destination targeting the page, or a form field
whose widget sits on it, for example — keeps that page and its content stream in
the extracted file, where anyone reading the bytes can recover it. Do not rely
on extraction to keep content confidential; see the [Redaction](redaction.md)
guide for removing it.
