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.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.