Help us shape the future of Go! Take the 2026 Go Developer Survey

ops

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

ops

CI Go Reference Go Report Card Coverage

The verbs of go-pdfkit: what people actually do to a PDF they already have. Merge two files, pull out pages three to seven, turn a page the right way up, drop the metadata, split a report into chapters.

Built on reader, which takes a file apart and puts one back together; nothing outside the Go standard library is used, so this builds for GOOS=js/wasm and every architecture the fleet targets.

The model

A document here is an ordered list of pages, each borrowed from a source file, plus the document-level pieces. Every operation rearranges that list or annotates its entries; nothing is applied until the document is written out. An operation therefore costs nothing until it has to, several files can be mixed freely, and — the reason the model is shaped this way — the list is a plain value that two people can edit at the same time.

What it does

go install github.com/go-pdfkit/ops/cmd/pdfops@latest

pdfops merge whole.pdf part1.pdf part2.pdf
pdfops select -pages 3-7,last report.pdf extract.pdf
pdfops delete -pages even scan.pdf fronts.pdf
pdfops rotate -pages all -by 90 sideways.pdf upright.pdf
pdfops crop -box 20,20,575,820 wide.pdf trimmed.pdf
pdfops split -every 10 book.pdf chapters/
pdfops reverse back-to-front.pdf right-way-round.pdf
pdfops nup -n 4 slides.pdf handout.pdf
pdfops booklet chapter.pdf to-fold.pdf
pdfops overlay -with letterhead.pdf plain.pdf headed.pdf
pdfops blank -before 3 report.pdf report-with-a-gap.pdf
pdfops watermark -text CONFIDENTIAL contract.pdf marked.pdf
pdfops number -format "{page} of {pages}" report.pdf numbered.pdf
pdfops bates -prefix ACME- -start 1 exhibits.pdf stamped.pdf
pdfops stamp -text "seen 25 August" -at top-right -bold notes.pdf seen.pdf
pdfops sanitize downloaded.pdf safe.pdf
pdfops flatten filled-form.pdf final.pdf
pdfops strip -annotations -bookmarks private.pdf clean.pdf
pdfops info file.pdf

A page range is written 1-3,7,10- and may say all, even, odd or last. It keeps its own order and its own repeats, so select -pages 3-1 reverses three pages and select -pages 1,1 gives you two copies.

-password opens an encrypted file; what is written out is not encrypted.

Links and bookmarks are carried over and pointed at the pages they became here, so extracting three pages of a book leaves the links between those three working and drops the ones that led out of them. A link or a bookmark that already led nowhere in the source keeps its place without a destination, because a file with broken bookmarks should keep its shape rather than lose it.

The catalogue is always written from nothing, so a document-level script, an action that runs on opening, or a tree of embedded files never survives any operation at all. sanitize deals with what travels attached to a page: its own actions, an annotation's actions, JavaScript, launching, form submission, the annotation types that exist to play or embed something, and files associated with a page. A link to the web is not executable and stays.

Text is drawn in the four faces every viewer already has — Helvetica, Helvetica-Bold, Courier and Courier-Bold — so nothing is embedded and a watermark costs about a kilobyte. Stamp text may say {page}, {pages} and {n}, the last being a counter for serial numbering.

Verified against real files

Every operation is checked on the same corpus of 118 863 real PDFs the reader is measured against — Matplotlib, cairo, pdfTeX, Ghostscript, Adobe, R, Apache FOP, PDF 1.3 through 1.7. For each of the 118 833 that open, four properties have to hold, compared on the bytes of every page's content stream rather than on an exit status:

  • writing the document unchanged reproduces every page;
  • reversing twice is the identity;
  • merging a document with itself doubles it exactly;
  • selecting the last page yields exactly that page.

All four hold on all 118 833 files.

Laying pages out is checked the same way: for each of the 1 959 multi-page files in that corpus, two-up produces the right number of sheets and every form drawn on them carries exactly the bytes of the page it stands for, blanks included. All 1 959 hold.

Testing

go test -covermode=set ./...

CI gates on exact 100% statement coverage, go vet, and a cross-compile across linux/{amd64,arm64,riscv64,loong64,ppc64le,s390x}, js/wasm, darwin/arm64 and windows/amd64.

License

BSD-3-Clause — see LICENSE. Copyright the go-pdfkit/ops authors.

Documentation

Overview

Package ops is the verb layer of go-pdfkit: what people actually do to a PDF they already have. Merge two files, pull out pages three to seven, turn a page the right way up, drop the metadata, split a report into chapters.

A document here is an ordered list of pages, each borrowed from a source file, plus the document-level pieces. Every operation rearranges that list or annotates its entries, and nothing is applied until Doc.Bytes is called. An operation therefore costs nothing until it has to, pages from several files mix freely, and the list stays a plain value.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseRange

func ParseRange(spec string, pageCount int) ([]int, error)

ParseRange turns the page selection people write — "1-3,7,10-", "even", "last", "all" — into page numbers counting from one, in the order given and with duplicates kept, because "1,1,2" really does mean three pages.

A range with no start begins at the first page and one with no end runs to the last. A descending range like "5-2" counts down, which is how a reversed extract is written.

Types

type Doc

type Doc struct {
	// contains filtered or unexported fields
}

A Doc is a document being assembled: pages in the order they will be written, and the trailer's information dictionary.

func FromDocument

func FromDocument(src *reader.Document) *Doc

FromDocument wraps an already-parsed document. Every page the document reports can be read — the page tree walk has already established that — so there is nothing here that can fail.

func Merge

func Merge(docs ...*Doc) *Doc

Merge joins documents in the order given.

func New

func New() *Doc

New returns an empty document, for merging into.

func Open

func Open(b []byte) (*Doc, error)

Open reads a PDF file held in memory, with the empty password.

func OpenWithPassword

func OpenWithPassword(b []byte, password string) (*Doc, error)

OpenWithPassword reads a PDF file that may be encrypted.

func (*Doc) Append

func (d *Doc) Append(other *Doc)

Append adds the pages of another document to the end of this one. The other document is not changed, and the two may come from different files.

func (*Doc) Bates

func (d *Doc) Bates(spec, prefix string, start, digits int) error

Bates numbers the pages a range names with a running serial, the way a legal exhibit is marked: a fixed prefix and a zero-padded counter.

func (*Doc) Blank

func (d *Doc) Blank(width, height float64)

Blank adds an empty page of the given size at the end.

func (*Doc) Booklet

func (d *Doc) Booklet() error

Booklet reorders the pages for saddle-stitch printing — folded in half and stapled through the spine — and lays them two to a sheet. Blank pages are added so the count is a multiple of four, which is what folding needs.

func (*Doc) Bytes

func (d *Doc) Bytes() ([]byte, error)

Bytes writes the document out as a PDF file. Every borrowed page is copied out of the file it came from with the attributes it inherited written onto it, so pages from different documents keep their own geometry and resources.

func (*Doc) ClearInfo

func (d *Doc) ClearInfo()

ClearInfo drops the whole information dictionary, which is what "remove the metadata" means.

func (*Doc) Crop

func (d *Doc) Crop(spec string, box [4]float64) error

Crop sets the visible area of the pages the range names, in points, as [left bottom right top]. It sets the crop box; the media box, which is the paper the page is on, is left alone.

func (*Doc) Delete

func (d *Doc) Delete(spec string) error

Delete removes the pages the range names.

func (*Doc) DropOutlines

func (d *Doc) DropOutlines()

DropOutlines leaves the bookmarks behind. They are kept by default: a merge that loses every bookmark is a poor merge.

func (*Doc) Flatten

func (d *Doc) Flatten()

Flatten draws each annotation's own appearance into the page and then drops the annotation, so what one reader sees is what every reader sees — which is what filling in a form and then flattening it means.

func (*Doc) Info

func (d *Doc) Info() reader.Dict

Info returns the document information dictionary, which may be nil.

func (*Doc) InsertBlank

func (d *Doc) InsertBlank(i int) error

InsertBlank puts an empty page before the page at i, counting from one; an index one past the end appends. The size is taken from the page it precedes, or from the one before it at the end of the document.

func (*Doc) Move

func (d *Doc) Move(from, to int) error

Move takes the page at from and puts it at to, both counting from one, the other pages closing up behind it.

func (*Doc) NUp

func (d *Doc) NUp(n int) error

NUp lays n pages on each sheet, in reading order. The sheet keeps the size of the first page, and the grid is whichever arrangement of at least n cells gives cells closest in shape to the pages going into them.

func (*Doc) Overlay

func (d *Doc) Overlay(other *Doc) error

Overlay draws each page of another document on top of the pages here, in order. When the other document has fewer pages its last page is not repeated: pages past its end are left alone.

func (*Doc) PageCount

func (d *Doc) PageCount() int

PageCount reports how many pages the document has.

func (*Doc) PageNumbers

func (d *Doc) PageNumbers(spec, format string) error

PageNumbers writes a number at the foot of the pages a range names. The format is a stamp's text, so "{page} of {pages}" and "— {page} —" both work.

func (*Doc) RemoveAnnotations

func (d *Doc) RemoveAnnotations()

RemoveAnnotations drops every annotation: links, comments, form fields and all. What was drawn on the page stays; what sat on top of it goes.

func (*Doc) Resize

func (d *Doc) Resize(spec string, box [4]float64) error

Resize sets the media box — the paper — of the pages the range names.

func (*Doc) Reverse

func (d *Doc) Reverse()

Reverse puts the pages in the opposite order.

func (*Doc) Rotate

func (d *Doc) Rotate(spec string, degrees int) error

Rotate turns the pages the range names by the given number of degrees, which must be a multiple of ninety. The rotation is relative to what the page already had, so rotating twice by ninety turns a page upside down.

func (*Doc) Rotation

func (d *Doc) Rotation(i int) (int, error)

Rotation reports the rotation of the i'th page, counting from one.

func (*Doc) Sanitize

func (d *Doc) Sanitize()

Sanitize strips the parts of a file that do something rather than show something: page and annotation actions, JavaScript, launching, form submission, files travelling with a page, and the annotation types that exist to embed or play something.

A document written by this package always carries a catalogue of this package's own making, so document-level scripts, automatic actions on opening and embedded file trees are gone whatever this setting says; sanitising deals with what travels attached to a page.

func (*Doc) Select

func (d *Doc) Select(spec string) error

Select keeps only the pages the range names, in the order it names them, so it extracts, reorders and duplicates in one verb.

func (*Doc) SetInfo

func (d *Doc) SetInfo(key reader.Name, value string)

SetInfo replaces one entry of the information dictionary. An empty value removes it.

func (*Doc) SetRotation

func (d *Doc) SetRotation(spec string, degrees int) error

SetRotation turns the pages the range names to an absolute angle.

func (*Doc) SetVersion

func (d *Doc) SetVersion(v string)

SetVersion sets the version written in the header.

func (*Doc) Split

func (d *Doc) Split(n int) ([]*Doc, error)

Split cuts the document into pieces of at most n pages each.

func (*Doc) SplitAt

func (d *Doc) SplitAt(at ...int) ([]*Doc, error)

SplitAt cuts the document before each of the given page numbers, which is how a report is broken into chapters.

func (*Doc) Stamp

func (d *Doc) Stamp(spec string, s Stamp) error

Stamp draws text on the pages a range names.

func (*Doc) Underlay

func (d *Doc) Underlay(other *Doc) error

Underlay draws each page of another document underneath the pages here.

func (*Doc) Version

func (d *Doc) Version() string

Version reports the PDF version that will be written.

func (*Doc) Watermark

func (d *Doc) Watermark(spec, text string) error

Watermark draws large, pale, slanted text across the pages a range names — the thing the word usually means.

type Font

type Font string

A Font names one of the four faces this package can draw with.

const (
	Helvetica     Font = "Helvetica"
	HelveticaBold Font = "Helvetica-Bold"
	Courier       Font = "Courier"
	CourierBold   Font = "Courier-Bold"
)

The faces available. Any other name reads as Helvetica.

func (Font) Width

func (f Font) Width(s string, size float64) float64

Width reports how wide a string is at the given point size, once encoded.

type Page

type Page struct {
	// contains filtered or unexported fields
}

A Page is one page of a document, borrowed from the file it came from. The rotation is what will be written, not what the source said.

type Position

type Position uint8

A Position says where on a page something goes.

const (
	Center Position = iota
	TopLeft
	TopCenter
	TopRight
	BottomLeft
	BottomCenter
	BottomRight
	MiddleLeft
	MiddleRight
)

The nine places a stamp can sit.

type Stamp

type Stamp struct {
	Text     string
	Font     Font
	Size     float64    // in points; zero means twelve
	Colour   [3]float64 // red, green and blue, each from zero to one
	Opacity  float64    // zero means fully opaque
	Rotate   float64    // degrees, anticlockwise, about the text's own centre
	Position Position
	Margin   float64 // distance from the edge, in points; zero means 24
	Start    int     // the first value of {n}; zero means one
	Digits   int     // {n} padded to this many digits
}

A Stamp is a line of text drawn on a page.

The text may name what it is being drawn on: {page} is the page's number, {pages} the number of pages, and {n} a counter that starts wherever the stamp says and advances once per page stamped, which is what a numbering scheme like Bates needs.

Directories

Path Synopsis
cmd
pdfops command
Command pdfops does to a PDF the things people actually want done to one: merge, extract, delete, reorder, rotate, crop, split, and read or strip the metadata.
Command pdfops does to a PDF the things people actually want done to one: merge, extract, delete, reorder, rotate, crop, split, and read or strip the metadata.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL