Skip to content
Flatten page rotation

Flatten page rotation

A page with Rotate 90 looks rotated in a viewer, but its content stream and its coordinates are untouched, so the visual top left corner is not where the origin is. Flattening rotates the content itself and sets Rotate back to 0. The page looks identical and is much easier to work with afterwards.

Reach for it before extracting positions, placing new content on a rotated scan, or handing pages to code that assumes an unrotated page. If you only want the page to appear turned, set Rotate instead; see rotate PDF pages.

Doing it

The work happens through creator.NewBlockFromPage, which turns a page into a repositionable block:

block, err := creator.NewBlockFromPage(page)
if err != nil {
    return err
}

w, h := block.RotatedSize()
block.SetPos((w-block.Width())/2, (h-block.Height())/2)

c.SetPageSize(creator.PageSize{w, h})
c.NewPage()
if err := c.Draw(block); err != nil {
    return err
}

NewBlockFromPage copies the page’s rotation into the block already, as -page.Rotate, so no SetAngle call is needed to flatten. The example’s explicit block.SetAngle(float64(-*page.Rotate)) sets the same value the constructor did.

The angle is negated because a Rotate of 90 means the viewer turns the page clockwise, and the content has to turn the other way to end up looking the same.

RotatedSize returns the bounding box of the block after rotation, which is the page size the output needs: a 90 degree rotation swaps width and height. The rotation is applied about the center of the block, so placing the unrotated box centered in the new page box, which is what the SetPos call computes, leaves the rotated content centered too.

Limitations

Everything goes through the block, and a block carries only the content stream and its resources. Annotations, links, form fields and page-level metadata are not part of it, so they are gone from the output. This is the general property of the creator route rather than something specific to flattening, and it is the main reason not to flatten a form or an annotated document.

The output is a new document assembled page by page, so document-level parts, outlines, document info, page labels and named destinations, are not carried over either.

NewBlockFromPage uses the media box, not the crop box. A page cropped to a smaller visible area comes out at full media box size with the previously hidden content visible. Crop it again afterwards if that matters; see crop page.

Rotation values that are not a multiple of 90 are passed through to the block angle unchanged. Nothing rejects them, and the result is a page whose content sits at an odd angle inside a bounding box that has grown to contain it.

page.Rotate is nil on any page that inherits its rotation from the page tree, and dereferencing it panics. NewBlockFromPage reads the field directly and treats nil as no rotation, so a page inheriting Rotate 90 from its parent flattens to nothing. page.GetRotate() is the accessor that walks up the tree; passing pages through PdfWriter.AddPage once, which copies inherited values down, is the other way to normalize a document before flattening.

Run the example

rotateFlattenPdf loops over the pages, builds a block from each, sizes the output page to the rotated bounds and draws the block centered. Rotation of 0 makes the whole thing a no-op, so it is safe to run over a document where only some pages are rotated.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/pages
go run pdf_rotate_flatten.go input.pdf 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
Last updated on