Skip to content

Reorder pages

Page order in a PDF is the order of the entries in the page tree, so changing it means reading the pages and adding them to a new writer in the order you want. There is no call that moves a page within an existing document.

The same loop covers reordering, dropping pages and extracting a range. Which pages you call AddPage with, and in what sequence, is the whole of it.

Doing it

pdfReader, f, err := model.NewPdfReaderFromFile(inputPath, nil)
if err != nil {
    return err
}
defer f.Close()

pdfWriter := model.NewPdfWriter()
for _, pageNum := range []int{3, 1, 2} {
    page, err := pdfReader.GetPage(pageNum)
    if err != nil {
        return err
    }
    if err := pdfWriter.AddPage(page); err != nil {
        return err
    }
}

return pdfWriter.WriteToFile(outputPath)

GetPage numbers pages from 1 and returns page numbering must start at 1 for 0, or invalid page number (page count too short) past the end. Reversing a document is the same loop counting down; dropping pages is the same loop with a condition.

reader.ToWriter is the other route from reader to writer and it is the better one for most edits, because it carries outlines, metadata and the AcroForm across. It cannot do this job: its callback runs once per page in the original order, and there is no hook for changing that order.

For a contiguous range, pdfutil.ExtractPageRange is the same loop already written:

err := pdfutil.ExtractPageRange(inputPath, outputPath, 1, 2, false)

pageFrom and pageTo are inclusive and 1-based. The last argument copies the document’s optional content properties (OCProperties) to the output, which matters only for files with layers.

Limitations

A hand-built PdfWriter starts empty, so everything document-level is gone from the output: outlines, document info, XMP metadata, page labels, named destinations, viewer preferences and the AcroForm. Page contents and annotations survive because they hang off the page dictionary. PdfWriter has a setter for most of these, AddOutlineTree, SetForms, SetDocInfo, SetPageLabels and SetNamedDestinations among them, so copying them across is possible but explicit.

Internal links break silently when their target is dropped. A link annotation pointing at a page that never made it into the output still writes out, and clicking it does nothing useful.

AddPage copies the inherited Resources, MediaBox, CropBox and Rotate values down onto the page dictionary itself, then re-parents the page to the new writer. That is what makes a page independent of its original document.

It also mutates the page object it is given rather than copying it. Adding the same *PdfPage twice puts one object in the page tree twice, which is not valid. Use page.Duplicate() for the second copy.

ExtractPageRange checks that pageTo is within the document but not that pageFrom is at least 1 or that pageFrom <= pageTo. A reversed range writes a document with no pages instead of returning an error.

Run the example

pdf_split.go is the range case: it reads the page numbers off the command line and hands them straight to ExtractPageRange. The reordering loop above is what the function does internally.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/pages
go run pdf_split.go input.pdf 1 2 output.pdf

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source

pdf_split_advanced.go in the same folder is the identical program with keepOptionalContent set to true.

Last updated on