Skip to content
Page Manipulation

Page Manipulation

Changing an existing document always means reading it with a PdfReader and writing a new one with a PdfWriter; there is no in-place edit. Which of the three routes from reader to writer you take decides what survives the round trip, and that is the decision worth getting right before writing any code.

RouteWhat you controlWhat you keep
reader.ToWriter(opts)Each page, through opts.PageProcessCallback. Page order is fixed.Everything: document info, metadata, outlines, AcroForm, page labels, named destinations, optional content, viewer preferences, structure tree, global rotation.
model.NewPdfWriter() plus AddPageWhich pages, and in what order.Pages only. Anything document-level you want has to be copied across by hand.
creator plus NewBlockFromPageGeometry: scale, rotate, place several source pages on one output page.Page content and its resources. Annotations, links and form fields are dropped.

ToWriter is the default. The callback runs once per page, in order, before the page is added to the writer, and returning an error from it aborts the whole conversion:

pdfWriter, err := pdfReader.ToWriter(&model.ReaderToWriterOpts{
    PageProcessCallback: func(pageNum int, page *model.PdfPage) error {
        page.Rotate = nil // for example
        return nil
    },
})
if err != nil {
    return err
}

err = pdfWriter.WriteToFile(outputPath)

ReaderToWriterOpts also carries a Skip* flag for each document-level part, so you can drop outlines or the AcroForm deliberately rather than by accident.

Inherited page attributes

Resources, MediaBox, CropBox and Rotate are inheritable: a page dictionary that omits them takes the value from its parent node in the page tree. This shows up in two places.

When reading, the struct field is nil if the value is inherited. page.MediaBox and page.Rotate are only set when the page carries them itself, so use page.GetMediaBox() and page.GetRotate(), which walk up the tree. page.Size() is built on both and also accounts for rotation, swapping width and height at 90 and 270 degrees.

When writing, PdfWriter.AddPage copies all four inherited values down onto the page dictionary itself. This is what makes a page independent of its original page tree, and it is also why PdfWriter.SetRotation often appears to do nothing - see rotate PDF pages.

Page boxes

A page has up to five rectangles. Only two matter for most work.

MediaBox is the physical sheet and is the only required one. CropBox is the region a viewer displays and defaults to the media box when absent. BleedBox, TrimBox and ArtBox are for print production and are ignored on screen.

Because the crop box wins for display when it is present, changing only the media box on a page that has a crop box will not change what anyone sees. Crop page covers this.

Where to look

GuideCovers
Get page infoPage size, rotation and boxes, and inherited values.
Crop PDF pagesShrinking the visible area of every page.
Rotate PDF pagesGlobal rotation, per-page rotation, and why the two conflict.
Flatten page rotationBaking rotation into the content so the origin is the top left corner.
Merge PDFsCombining files, with and without form fields.
Reorder pagesWriting pages out in a different order, or dropping some.
Add a side note to a page marginDrawing in the margin of every page at generation time.

For splitting a document into page ranges, pdfutil.ExtractPageRange is the one call version; see reorder pages for the pattern it uses. For stamping text or images over existing pages, see watermarks.

Last updated on