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

ops

package module
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: BSD-3-Clause Imports: 15 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 resize -to a4 odd-sizes.pdf a4.pdf
pdfops resize -to a4 -landscape slides.pdf wide.pdf
pdfops move -from 1 -to 3 misplaced.pdf ordered.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 interleave both.pdf fronts.pdf backs.pdf
pdfops duplex scan.pdf document.pdf
pdfops onepage strip.pdf one-long-page.pdf
pdfops poster -across 2 -down 2 plan.pdf tiles.pdf
pdfops divide -across 2 spread.pdf pages.pdf
pdfops overlay -with letterhead.pdf plain.pdf headed.pdf
pdfops underlay -with watermark.pdf report.pdf behind.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 compress fat.pdf lean.pdf
pdfops encrypt -user letmein -allow print,copy plain.pdf locked.pdf
pdfops -password letmein decrypt locked.pdf plain.pdf
pdfops -password letmein permissions locked.pdf
pdfops text paper.pdf
pdfops text -layout -pages 1 paper.pdf
pdfops text -layout -json paper.pdf
pdfops images paper.pdf pictures/
pdfops attachments report.pdf
pdfops attachments -to files/ report.pdf
pdfops attach -file figures.csv -description "the figures" report.pdf with.pdf
pdfops detach -name figures.csv with.pdf without.pdf
pdfops outline -from toc.txt book.pdf bookmarked.pdf
pdfops outline -drop bookmarked.pdf plain.pdf
pdfops info file.pdf
pdfops metadata -set 'Title=The Report' -set Author=Someone draft.pdf titled.pdf
pdfops metadata -clear draft.pdf anonymous.pdf
pdfops version -set 1.7 old.pdf declared.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.

interleave takes one page from each file in turn, which is how two single-sided scans — the fronts and the backs — become one double-sided document. onepage stacks every page onto a single long one. poster cuts each page into tiles so a plan can be printed larger than the paper and assembled.

deblank finds the pages that hold nothing and drops them. It draws each page and counts the pixels that are not paper, because that is the only test that agrees with a reader: a page whose content stream is full of operators painting white, or drawing outside the media box, looks blank and is blank, and a page carrying a picture of nothing is blank too.

pdfops deblank -list scan.pdf          # say which, write nothing
pdfops deblank scan.pdf tidied.pdf

⛔ The default resolution is 150 DPI, and it was raised from 50 because of a measurement. On A4:

50 DPI 150 DPI
the page 414 × 585 1240 × 1755
one pixel 4.1e-06 of it 4.6e-07
an 8-point full stop 1 pixel 4 pixels
an 8-point comma 2 pixels 8 pixels

At 50 DPI a full stop is exactly the mark a speck of dust makes, so no threshold at that resolution can tell punctuation from dirt. At 150 there is a factor of four to set -max-ink from, and TestMeasureTheInkLadder prints the whole ladder so that a renderer change which moved either end fails there instead of silently reclassifying somebody's pages.

⛔ The zero value is the strictest setting, not a middle one: a page with a single inked pixel is kept. This is the input to deleting pages — a page wrongly called blank destroys content, while a blank page wrongly kept merely leaves a blank page. For scans, -max-ink 1e-6 is about two pixels: it forgives a speck and still keeps a page holding a full stop. It is not the default because whether that margin holds depends on the scanner, which this package cannot see.

Three more things it will not do:

  • it will not call a page of solid colour blank. The test is distance from white, not uniformity; a measure of variance would delete a deliberate black separator sheet;
  • it will not empty the document. Every page blank is an error, because a file of no pages still opens and still has a size, and a caller handed one would be told nothing was wrong;
  • it does not count annotations as ink unless asked: a page whose only mark is a sticky note is blank paper somebody wrote on.

And -list writes nothing and takes no output file, because the first thing anybody should do with a tool that deletes pages is ask it which ones it means.

⛔ divide makes the same grid as poster and is the opposite operation. poster keeps every piece the size of the page it came from, so it enlarges: the pieces taped together make one page the size of a wall. divide keeps the content at its own size and makes the pages smaller, so the pieces laid side by side are the page again at 1:1 — which is what a two-page spread scanned as one wide sheet, or two tickets printed on one page, needs.

Confusing them gives a result that looks right in a thumbnail and is wrong by a factor of across and down. The tests assert the media box, not the page count, and TestDivideIsNotPoster compares the two directly: without it, every assertion about divide could be satisfied by poster.

duplex is the same job when the scanner left one file rather than two: a feeder that cannot turn a sheet over gives every front, then every back. It cuts the file in the middle and interleaves the halves.

⛔ The backs are reversed by default, and the flag is -keep-backs rather than -reverse-backs so that the default is the common case: the stack is turned over as a stack, so the last sheet's back is scanned first. A tool whose default assumed otherwise would quietly put every back on the wrong front — which reads as "the scan is fine, the order is odd" rather than as a reversal.

The default split is ceil(n/2), not n/2: an odd stack has its unpaired sheet at the end of the fronts, so thirteen pages split at seven. ⛔ The test for that had to be rewritten. The first version used a reversed back block — and with the backs reversed the output is the same for any split in a range, because the reversal absorbs the difference. It passed at the ceiling and at the floor, so it proved nothing; the negative control is what said so, and the assertion now uses -keep-backs, where the split is load-bearing.

repair writes a file back out with its structure rebuilt, and says whether rebuilding was needed. -check answers that question and writes nothing.

pdfops repair -check suspect.pdf
pdfops repair broken.pdf fixed.pdf

⛔ The rebuilding is not this verb's doing. go-pdfkit/reader repairs a damaged file on its own and quietly, which is the right behaviour for a reader — a caller asking for page three wants page three, not a lecture. What was missing was a way to say it happened and to keep the result, and that is the whole of this verb. Doc.Repaired() is the same answer in Go.

It reports on any source, not all: a merged document's pages come from several files, and one damaged source is the answer to "did this need repairing". A true that depended on every source being broken would be false for every real merge.

⛔ And the scope is reader's, not QPDF's. What it recovers is a cross-reference table that is wrong, missing, or points into the middle of something else — the commonest damage, and the kind where the objects are all still there. It is not a general salvage engine, and a file whose objects are themselves truncated may still be unreadable. A file that did not need repairing is said out loud and is not an error, so that "repair everything in this directory" can be scripted.

labels sets what a viewer shows in its toolbar — "iv of 320" — which is never what is printed on the paper. For a number on the page, that is number above.

pdfops labels -list book.pdf
pdfops labels -rule 'pages=1-12;style=r' -rule 'pages=13-;style=D' book.pdf out.pdf
pdfops labels -rule 'pages=all;style=D;prefix=A-;start=1' appendix.pdf out.pdf
pdfops labels -clear book.pdf plain.pdf

The six styles are the format's own: D (1, 2, 3), r (i, ii, iii), R (I, II, III), a (a, b, c), A (A, B, C), and an empty style, which is the prefix alone — how a run of pages is marked "Cover" or "Insert".

⛔ A page-label range in a PDF is CONTIGUOUS. The file holds the first page of each run and the viewer counts forward from there, so a rule over 1-9,30-40 has to be compiled into two runs. Without continue the second run starts again at start; with it, it carries on. pages=odd is the extreme of the same thing — fifty runs of one page over a hundred — and both answers are expressible only because a rule is compiled rather than stored as written.

⛔ A page-label tree has no gaps either. An entry labels from its own key until the next entry, so a document labelled 1, 2 and then meant to stop comes back 1, 2, 3, 4 unless the stop is said out loud: an entry with neither a style nor a prefix. The first draft skipped unlabelled pages and the round-trip caught it.

⛔ start must be at least 1, and that is the format's rule rather than a choice here: ISO 32000-1 table 159 says /St "shall be greater than or equal to 1". A zero-based numbering scheme is not expressible as a numeric style, so a start below one is refused rather than written out of specification — a file that opens in one viewer and renumbers itself in another is worse than a refusal. BentoPDF's own tool offers "set to 0 for zero-based numbering", and what that writes is a value the standard forbids.

⛔ A page label's letters are not a spreadsheet's columns. Table 159 says the letters run "A to Z for the first 26 pages, AA to ZZ for the next 26" — so 27 is AA and 28 is BB, where a spreadsheet's 28 is AB. Borrowing the spreadsheet rule, which is the one everybody has written before, relabels every appendix past the twenty-sixth.

Roman numerals stop at 3999, where the additive notation runs out — 4000 needs a bar over the M, which neither a PDF string nor a page number has — and past it the number is written in decimal. A label a viewer shows wrongly is worse than one it shows in the wrong style.

⛔ Every verb used to drop the page labels

A /PageLabels tree is keyed by page index, so it cannot be carried across a rebuild the way the other document keys are: after a select or a reverse every label would be on the wrong page. So this package dropped it — and a document whose front matter was numbered i, ii, iii came back from a watermark numbered 1, 2, 3.

The labels are now asked for per page and rebuilt, which is right in both directions: a verb that keeps the pages keeps their labels, and a verb that moves them moves the labels with them. A merge keeps each source's labels on its own pages.

The runs are compressed on the way out: three hundred decimal pages are one entry, not three hundred. A label no style can explain becomes a run of one with the whole label as a literal prefix — the file then says exactly what the viewer must show, at the cost of an entry.

layers is the optional content a viewer calls layers: a drawing's dimensions, its markup, its alternatives.

pdfops layers -list plan.pdf
pdfops layers -hide 'Internal pricing' plan.pdf out.pdf
pdfops layers -add 'Markup/Mine' plan.pdf out.pdf

⛔ Every verb used to lose them. Measured before this: a one-page drawing with a layer switched off, run through pdfops watermark, came back with no /OCProperties at all — the content still marked as belonging to a layer, the layer object still reachable through the page's own resources, and nothing left saying which layer was hidden. So the hidden layer showed. A drawing distributed with an "internal pricing" layer turned off revealed it to any verb at all.

The layers are now constructed on the way out rather than carried, which is what lets them survive a reorder and a merge: keepCatalogue refuses to merge two catalogues, because two forms whose fields are named the same cannot honestly become one — but two drawings' layers can, since a layer is its object and two layers named "Markup" are two layers.

What the file says about its own layers, and is obeyed
/BaseState OFF inverts the question: everything is hidden unless /ON names it. A reader that only looked at /OFF would report every layer of such a document as visible — and a drawing that ships with all its layers off but one is exactly how an alternatives document is built
/Locked the document asking that a layer not be switched. -hide on one is refused: a drawing's dimension layer is what everything else is positioned against, and switching it anyway ignores the only thing the file says
/D /Order the nesting, which is written back nested. A string inside it is the group's label rather than a layer

⛔ Dropping a hidden layer is refused, because what was drawn on it does not go with it: the content stays in the page and stops being optional, so the hidden drawing becomes permanently visible — the opposite of what somebody deleting a layer called "internal pricing" expects. Show it first if that is what you mean.

⛔ A name is not an identifier. A drawing assembled from two others often has two "Markup" layers, and switching one of them at random is a change nobody asked for and nobody can see, so an ambiguous name is refused with the count.

attachments lists the files a document carries, and writes them out with -to. A name comes out of the document rather than from whoever runs the command, so it is stripped to its last element: ../escaped.txt is written inside the directory asked for, and a name with no usable last element is refused rather than skipped. detach refuses a name the file does not carry, instead of writing an unchanged copy somebody then sends believing it was removed.

outline replaces the bookmarks from a file, or removes them with -drop. The file is one bookmark a line — a title, a tab, a page number — with two spaces of indent for each level of nesting:

Introduction	1
  Why	2
Chapter one	7

A line it cannot read is an error naming the line, because a table of contents quietly missing an entry is worse than one that refuses to be written.

resize sets how big a page is; crop sets what of it shows. Two verbs, because they are two questions. A size is a box in points, or one of a3, a4, a5, letter, legal, tabloid — derived from their definitions rather than typed, so A4 is 210 × 297 mm at 72 points to the inch and not a number somebody copied. -landscape turns a named size on its side.

metadata writes what info prints. A key is given the way the PDF names it, with or without the leading slash, because a person reading info sees Title and should be able to type what they saw.

text -json writes the same reading as the lines, in a shape something else can parse without splitting on tabs. With -layout each run carries its position, its size, and whether it was invisible or unreadable — and those two are written even when false, because a reader that cannot tell "this run is readable" from "this tool did not say" is reading a guess.

text reads the page back as words, and -layout says where every piece of it sits — a page, a place, a size, and what it says. A piece the document gave no way to read comes back marked rather than guessed at, so you can tell a page that says nothing from one that could not be read. images writes out the pictures a page places, a JPEG as a JPEG.

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.

What a screen reader follows

A tagged document says which run of marks on which page is a heading, a paragraph, a table cell, the label of a form field. That is the reading order a screen reader follows, and for a government form it is often what the law requires — RGAA in France, Section 508 in the United States, EN 301 549 in the EU. It cannot be copied across a rebuild: every part of it points into the document, and the number tree that indexes it is keyed by a number each page carries. So it is rebuilt, element by element, around the pages that survived, and those numbers are handed out afresh. A page that kept the number it had in a file it is no longer part of is a page a reader would look up and be told, with every confidence, about somebody else's.

An element whose every page has gone is removed and its parent pruned; one with some pages gone keeps the children that are left. An empty table cell is kept, because the shape of a table is part of what it says. A page written twice carries the structure once, on the first copy, since an element names one page. Merging two files carries no tree at all: two files have two role maps in which the same name may mean two different things, and there is no honest way to choose between them.

Of 1 633 real government forms from eighteen issuers, 1 021 carry a structure tree. After a rotate 1 014 still do — the seven that do not are the seven whose tree was empty in the file we were given — and 991 of the 1 021 are identical down to the last element, mark and annotation reference. The thirty that differ are accounted for one by one in the commit that added this. What cannot be carried is named in catalogue.go with the reason, rather than disappearing quietly.

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.

⛔ What a rewrite carries, audited rather than assumed

Twice in one afternoon this package was found to be dropping a catalogue entry whose absence is not "no opinion" but a default — so the loss changed the document silently. So the whole list was measured: a document holding twenty-three catalogue entries, a watermark over it, and of each entry the question what would its absence MEAN?

/PageLabels the viewer numbers pages by position — a book's front matter came back 1, 2, 3 instead of i, ii, iii
/OCProperties everything optional becomes visible — a hidden layer was revealed
/OutputIntents no colour management: a prepress file lost the ICC profile it declares, and would print differently with nothing to say why
/URI relative links resolve against wherever the viewer thinks the document is — the link is not removed, it is silently pointed elsewhere

All four are carried now, the first two rebuilt (they cannot be copied: one is keyed by page index, the other names objects the content reaches another way) and the last two copied.

What is deliberately not carried, and why
  • /OpenAction, /AA — what the document does when it is opened. Dropping them is safer, and is what sanitize exists for: carrying them would carry code across a rebuild;
  • /Legal, /DSS — a signature's attestation and its validation data. A rewrite has already invalidated the signature the moment it changed a byte, and carrying the validation data of a signature that no longer verifies would say the document is still attested when it is not;
  • /Threads, /Collection — these point into the document, at pages and at embedded files, which is why /Names /Dests is not carried either.

⛔ And three the audit reported as lost that are not: /AcroForm, /Outlines and /StructTreeRoot are all carried. The audit's fixture held an empty one of each, and an empty form, outline or structure tree is deliberately not written. A measured absence has to be read before it is believed — which is the other half of why an audit is worth running.

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.

The rest of the family

reader reads and writes the format · ops is the verbs and the pdfops command · render turns a page into pixels · pdffont is what a document says about a font · extract reads a page back · coedit is a PDF several people edit at once · app is the browser workbench.

Documentation for all of it: https://go-pdfkit.github.io/docs/

License

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

Forms

pdfops fields lists what a form asks for and what it holds; pdfops fill fills it in and saves it.

$ pdfops fields fw9.pdf
note: the file also carries an XFA form, which is not read; the standard one is.
topmostSubform[0].Page1[0].f1_01[0]                       text      ""
topmostSubform[0].Page1[0].Boxes3a-b_ReadOrder[0].c1_1[0] checkbox  "Off"
                                                            buttons [1]

$ pdfops fill -set 'topmostSubform[0].Page1[0].f1_01[0]=Wolfgang Amadeus Mozart' \
              -set 'topmostSubform[0].Page1[0].Boxes3a-b_ReadOrder[0].c1_1[0]=1' \
              fw9.pdf filled.pdf

A filled form is written as an incremental update: the original file, byte for byte, with the objects that changed appended after it and a new cross-reference section pointing back at the old one. That is how every program that saves a form saves one, and it is the safest thing a program can do to somebody's document — nothing already there is rewritten, so whatever this does not understand survives untouched, and if the update is wrong the original is still the first part of the file.

The update says where its objects went the same way the file already does. A file whose cross-reference is a stream cannot be pointed back at by a plain table: a reader following /Prev would find an object where it expected the word xref. That is not a nicety — macOS's own renderer draws nothing at all for such a file, which is how the mistake was found.

Two things it will not do. A document that had to be repaired to be read has no cross-reference section worth pointing back at, so it is refused rather than added to. A document that is encrypted has every string and stream in it written through a key, and this does not yet write into one.

Measured on a real form: fw9.pdf filled in every field, written out, read back with every value in place, and rendered by macOS — which drew what we drew, in the same places, comb cells and ticks included.

⚠ Filling a form is the only verb that keeps one. Every other verb here takes the pages apart and builds a new document round them, and a form is tied into a document by object number in a dozen places at once — so merging, splitting or rotating a form's pages loses the form. Use fill on the file itself.

The form inside a blank document

Some PDFs are blank to look at and are not blank. Their pages hold one panel — "Please wait... your PDF viewer may not be able to display this type of document" — and the real form exists only as XML beside them, laid out when Adobe's own reader opens it. Adobe removed the format from PDF 2.0; no browser and no other reader lays one out.

pdfops xfa lays it out and draws it, which turns such a file into one anything can show.

$ pdfops xfa t1135-fill-23e.pdf readable.pdf
6 sheets, 497 elements drawn

Measured over every dynamic form in a corpus of 2 240 real government forms — fourteen of them, from the Canada Revenue Agency, the French cerfa series, the US Department of Labor and the Office of Personnel Management: all fourteen laid out, 111 sheets, 9 808 elements, nothing left unplaced, and every file read back by poppler.

WHERE each element goes is go-pdfkit/xfa's, measured against pdf.js and pdfium. What is drawn INSIDE each box is this package's own and much simpler: the four standard faces, a greedy line break, a rule round each field. So this is legibility rather than fidelity — the difference between a blank sheet and a form somebody can read, not between this and Adobe.

Pictures are drawn — 27 of them across twelve of the fourteen. For most that is a logo; for French cerfa 12064 it is the whole printed form. That one carries 212 fields, 7 draws, 4 images and — counted, not guessed — no <text> and no <caption> whatever, so without its pictures it is a grid of empty boxes and with them it is the customs declaration it is. How big each one is drawn follows pdfium's own arithmetic (xfa/fxfa/cxfa_ffwidget.cpp:55-86), because pdf.js declines to settle fit and actual and leaves them to a browser's sizing of an <img>.

A picture's own size is its pixels at its own resolution, read from a PNG's pHYs chunk or a JPEG's JFIF density, and that is not a detail. Every Canada Revenue Agency form in the corpus writes aspect="actual", and their logos are 1200-dpi PNGs: 2617 pixels across is 157.02 points, which is to a hundredth of a point the width the template writes for the box. Taken at 72 dpi — one point per pixel, the assumption for a picture that declares nothing — the same logo is 2617 points wide and covers a third of the sheet in black. It did, until the render was looked at.

Page numbers come out right — "Page 3 of 11", and "Page 3 sur 4" on a French form — without running the form's scripts. A footer like that is not text: it is rich text holding a floating field naming a hidden field whose value a script sets. All sixteen of those in the corpus ask one of exactly two things:

this.rawValue = xfa.layout.page(this);
this.rawValue = xfa.layout.pageCount();

Those are questions asked of the layout, which has just answered them. Recognising the two and handing back what the pager computed is not evaluating anything. Neither reference does this — pdf.js's layout ignores xfa:embed entirely, pdfium runs a JavaScript engine — so it is named as ours. Anything else a floating field names is left as a gap, because a wrong number that looks right is worse than a blank: four cerfa fields carry a static default beside their script, and printing that would put "Page 1 of 1" on every sheet.

Two limits, both named rather than left to be discovered:

  • A picture in a file outside the document is not fetched. That is pdf.js's position and its words: "we don't get remote data and use what we have in the pdf itself, so no picture for non null href". Two of cerfa 12818's are like that, and both name an absolute path on the machine of whoever drew the form — C:\Users\…\Downloads\logo ministere.PNG. They are reported, not passed over.
  • Text is set smaller rather than cut off. The box was measured with the font the template names and this draws in Helvetica, which is wider; at the template's own size the words come to more lines than the box holds. The size comes down until they fit, with a floor of four points, because losing the end of a sentence is worse than setting it a point smaller.

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 BlankPages added in v0.15.0

func BlankPages(pdf []byte, opt BlankOptions) ([]int, error)

BlankPages reports which pages of a file hold almost nothing, counting from one and in order.

Each page is DRAWN and its pixels counted, because that is the only test that agrees with a reader. A page whose content stream is full of operators painting white, or drawing outside the media box, looks blank and is blank; a page carrying an image of nothing is blank too, and no amount of reading the content stream says so.

⛔ What it will not do is call a page of solid colour blank. The test is distance from WHITE, not uniformity: a deliberately black separator sheet carries information, and a measure of variance would delete it.

func FromXFA added in v0.12.0

func FromXFA(src *reader.Document) (*Doc, *XFAReport, error)

FromXFA lays out the XFA form a document carries and returns a document of its pages, drawn.

The source document's own pages are not kept: for a dynamic form they are the placeholder, and for a static one they are the form itself and this is the wrong verb — see forms.Form.Dynamic, which says which kind a document is. This does not refuse a static one, because a caller may legitimately want to see what the XML says as against what the pages show, but a caller that has not asked which kind it has will replace a perfectly good form with a plainer drawing of it.

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 Attachment added in v0.10.0

type Attachment struct {
	// Name is what the document calls it.
	Name string
	// Description is what it says the file is, when it says anything.
	Description string
	// Data is the file.
	Data []byte
}

An Attachment is a file travelling inside the document.

type BlankOptions added in v0.15.0

type BlankOptions struct {
	// DPI is how finely the page is drawn before its pixels are counted. Zero
	// means a hundred and fifty.
	//
	// ⛔ It is not fifty, which is what this first used and what a thumbnail
	// would want. MEASURED on A4: at 50 DPI an eight-point full stop covers
	// exactly ONE pixel, which is the same mark a speck of dust makes, so no
	// threshold at that resolution can tell punctuation from dirt. At 150 it
	// covers four and a comma covers eight. See TestMeasureTheInkLadder.
	DPI float64

	// Tolerance is how far a channel may be from white and still count as
	// paper, out of 255. Zero means twelve.
	//
	// ⛔ Not zero-means-zero: nothing in a rendered page is exactly white.
	// Anti-aliasing, a JPEG background and a scan of real paper all land a few
	// levels below it, and a tolerance of zero calls every page inked.
	Tolerance uint8

	// MaxInk is the fraction of a page's pixels that may be ink and have the
	// page still count as blank. The zero value keeps any page with a mark on
	// it, which is what the zero value SHOULD mean here.
	//
	// For scans, where dust and the edge of the platen leave specks, the
	// figures to set it from are these, measured on A4 at the default 150 DPI:
	//
	//	one pixel            4.6e-07
	//	a full stop (8pt)    1.8e-06   (four pixels)
	//	a comma (8pt)        3.7e-06   (eight pixels)
	//	one word (12pt)      2.0e-04
	//
	// So 1e-06 tolerates a speck or two and still keeps a page holding a full
	// stop. The margin is a factor of four, and it is not offered as a default
	// because whether that margin holds depends on the scanner, which this
	// package cannot see.
	MaxInk float64

	// MaxPixels is passed to the renderer, so a media box a file made up
	// cannot be asked to fill memory.
	MaxPixels int

	// KeepAnnotations counts the notes, highlights and form fields a page
	// carries as ink. They are NOT counted by default: a page whose only mark
	// is a sticky note is blank paper somebody wrote on, and the note is not
	// part of what was printed.
	KeepAnnotations bool
}

BlankOptions say how little a page may hold and still count as blank.

⛔ The zero value is the STRICTEST setting, not a middle one: a page with a single inked pixel is kept. That is deliberate and it is the only safe default, because this is the input to deleting pages — a page wrongly called blank destroys content, while a blank page wrongly kept merely leaves a blank page.

type Bookmark added in v0.4.0

type Bookmark struct {
	Title    string
	Page     int
	Children []Bookmark
}

A Bookmark is an entry of an outline written from scratch: what it says, the page of this document it points at counting from one, and whatever sits under it.

It is what a document assembled rather than merged carries — a shared edit, a report built out of pieces — where there is no source outline to carry over because the outline is the caller's own.

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) AddLayer added in v0.15.0

func (d *Doc) AddLayer(name, parent string) error

AddLayer adds an empty layer, under parent when parent names one.

⛔ The layer has NO CONTENT on it, and that is what adding one means: a template gains a layer for whoever draws on it next. Nothing in the document becomes hidden or shown by adding one.

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) Attach added in v0.10.0

func (d *Doc) Attach(name string, data []byte, description string) error

Attach puts a file inside the document, under a name.

The name is what a reader shows and what another tool looks it up by. Two files under one name is a document that has lost one of them, so a name already used is refused rather than quietly replacing what is there.

func (*Doc) Attachments added in v0.10.0

func (d *Doc) Attachments() []Attachment

Attachments are the files the document carries, in the order it names them.

A PDF can hold whole files inside it — the spreadsheet a report was drawn from, the XML a form was filled from, the source of a figure. They are not drawn and nothing on the page says they are there, which is why a tool that rewrites a document can drop them without anyone noticing until the file is wanted.

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) Compress added in v0.2.0

func (d *Doc) Compress()

Compress asks for the file to be written with its objects packed into compressed streams and a cross-reference stream, which is what every writer since PDF 1.5 does and what makes a file a good deal smaller. It costs nothing but a version of 1.5, which every reader in use has understood for twenty years.

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) Decrypt added in v0.2.0

func (d *Doc) Decrypt()

Decrypt writes the file without protection. A document opened with the right password is already decrypted, so this only undoes an earlier call to Doc.Encrypt; a file read with OpenWithPassword and written out is unprotected either way.

func (*Doc) Delete

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

Delete removes the pages the range names.

func (*Doc) Detach added in v0.10.0

func (d *Doc) Detach(name string) bool

Detach removes the file of that name, and says whether there was one.

func (*Doc) Divide added in v0.15.0

func (d *Doc) Divide(across, down int) error

Divide cuts each page into an across×down grid of smaller pages, each the size of the piece it holds.

It is for a page that holds things which should have been separate: a two-page spread scanned as one wide sheet, two tickets printed side by side, a sheet of labels. Dividing a wide spread down the middle with Divide(2, 1) gives two pages of half the width, each of them the page it was before somebody put two on a sheet.

⛔ This is NOT Doc.Poster, although the grid is the same. Poster keeps every sheet the size of the page it came from, which means it ENLARGES: the pieces printed and taped together make one page the size of a wall. Divide keeps the content at its own size and makes the pages smaller, so the pieces laid side by side are the page again at 1:1. Confusing the two gives a result that looks right in a thumbnail and is wrong by a factor of across and down — which is why the distinction is in both doc comments rather than left to whoever reads the grid arguments.

The pieces come out in reading order: left to right, top to bottom, the top-left one first.

The cuts meet exactly. Nothing is repeated from one piece to the next and no bleed is left, because Divide is told how many pieces to make and not what a trimmer needs; every part of the page lands on one piece and one only, so the pieces are exactly the page again.

func (*Doc) DropBlank added in v0.15.0

func (d *Doc) DropBlank(opt BlankOptions) ([]int, error)

DropBlank removes the pages that hold almost nothing and reports which went, numbered as they were BEFORE the removal — because a caller who wants to say what happened needs the numbers the reader saw.

⛔ It refuses to empty the document. A file of no pages is still a file: it opens, it has a size, and a caller who handed over a stack of scans that turned out to be blank would be told nothing was wrong while being given something no reader can show. Every page blank is a result worth an error.

func (*Doc) DropLayer added in v0.15.0

func (d *Doc) DropLayer(name string) error

DropLayer removes a layer.

⛔ WHAT WAS DRAWN ON IT DOES NOT GO WITH IT. The content stays in the page and stops being optional, so a HIDDEN layer that is dropped becomes permanently visible — which is the opposite of what somebody deleting a layer called "internal pricing" expects. Removing the content as well would mean rewriting every content stream that mentions the layer, which this does not do; the refusal below is the honest half of that.

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) DuplexCollate added in v0.15.0

func (d *Doc) DuplexCollate(split int, reverseBacks bool) error

DuplexCollate puts a duplex stack back in reading order.

An automatic feeder that cannot turn a sheet over leaves ONE file holding two blocks: every front, then every back. This cuts it at split and interleaves the two halves, so front1, back1, front2, back2 and on.

A split of zero or less means the middle, rounded UP — ceil(n/2) — because an odd stack has the unpaired sheet at the END of the fronts, not the start of the backs. Thirteen pages split at seven, not six.

⛔ reverseBacks is not a convenience, it is the common case: the stack is turned over AS A STACK, so the last sheet's back is scanned first. Without it a thirty-sheet batch comes out with every back on the wrong front, which is a defect that reads as "the scan is fine, the order is odd" rather than as a reversal.

Blocks of different sizes do not stop it: the pairs collate first and whatever is left over follows in order, which is what the last single-sided sheet of a batch leaves behind.

func (*Doc) Encrypt added in v0.2.0

func (d *Doc) Encrypt(e reader.Encryption)

Encrypt protects the file that will be written. Two people can open it: whoever knows the user password, subject to the permissions, and whoever knows the owner password, subject to nothing.

An encrypted file is not byte-for-byte reproducible — encryption needs randomness, by design — so a document written twice with the same call comes out different both times, and neither can be compared with the other.

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) Interleave added in v0.9.0

func (d *Doc) Interleave(others ...*Doc) error

Interleave takes pages from each document in turn — the first of this, the first of the next, and round again — until every one of them is used up.

It is what a duplex scanner leaves behind. A stack fed through a single-sided feeder twice comes out as two files: the fronts in order, and the backs, which is the same document taken apart. Merging them end to end gives every front then every back; this gives the document.

The backs are usually in reverse: the stack is turned over as a stack, so the last sheet's back is scanned first. Reverse that one before interleaving and the pages land in the right order.

Documents of different lengths do not stop it. A shorter one simply runs out, and the rest of the longer one follows in order — which is what a scanner leaves when the last sheet is single-sided.

func (*Doc) Layers added in v0.15.0

func (d *Doc) Layers() []Layer

Layers reports the document's layers, in the order the document asks them to be shown and with their nesting.

A document with no optional content has none.

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) OnePage added in v0.9.0

func (d *Doc) OnePage() error

OnePage puts every page on a single sheet, one below the other.

A page is a unit of paper, not a unit of reading: a receipt, a chat log and a web page cut into A4 are one thing that a printer divided. This puts them back, so what is read scrolls rather than turns.

The sheet is as wide as the widest page and as tall as all of them stacked. Pages narrower than the widest are centred, because a column of text that jumps from side to side is harder to read than one that does not.

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) PageLabels added in v0.15.0

func (d *Doc) PageLabels() []string

PageLabels reports what each page of the document is labelled, in order, as a viewer would show it. A page with no label comes back as "".

It answers for the document as it stands: the labels a caller set, or the ones carried over from the files the pages came from.

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) Picture added in v0.8.0

func (d *Doc) Picture(data []byte, dpi float64) error

Picture adds a page holding one image, the size of the image.

This is the other half of drawing a page: a scanner, a camera and a screenshot all produce a picture, and what people want of a PDF toolkit is to be handed one back with the picture in it.

Every format go-gfx/gfx reads is accepted, which is more than a PDF can carry. A JPEG is put in as it stands, because a PDF carries JPEG and re-encoding one loses a little of it for nothing; everything else is decoded and written as samples, compressed. An image with any transparency in it gets a soft mask, so a PNG drawn over the page's white does not come out with black behind it.

dpi says how many of the image's pixels go into an inch of paper; 0 means 72, one point per pixel. A photograph from a telephone at 72 is a page the size of a wall, and at 300 it is a photograph.

func (*Doc) Poster added in v0.11.0

func (d *Doc) Poster(across, down int) error

Poster spreads each page over across×down sheets, so that the sheets printed and taped edge to edge make one page the size of a wall. It is the inverse of Doc.NUp, which puts several pages on one sheet.

Every sheet is the size of the page it came from. A poster of an A4 document therefore prints on A4, and each piece has the page's own proportions — pieces of some other shape do not tape back into the page they were cut from. Because the sheets keep their shape, a wall of them is only the page's shape again when across and down are equal; otherwise the page is enlarged as far as it will go inside the wall and centred, and the sheets at the edges carry the white that is left over.

The sheets meet exactly: nothing is repeated from one to the next, and no margin is left for taping. An overlap would have to be a constant here, because Poster is told how many sheets to use and not how wide the printer's unprinted border is; too small a guess still leaves a seam, too large a one loses more of the poster to trimming than it saves, and neither could be turned off. Meeting exactly is also the only arrangement that is exactly undone: every part of the page lands on one sheet and one only, so the pieces butt together — taped from behind — with nothing to cut away and nothing printed twice.

The sheets come out in reading order: left to right, top to bottom, the top-left one first, so a printed pile can be laid out on a table in the order it came off the printer.

func (*Doc) Protection added in v0.2.0

func (d *Doc) Protection() (reader.Protection, bool)

Protection reports how the file this document was read from was protected, and false when it was not protected at all — or when the document was not read from a file. It says nothing about how the document will be written: that is what was passed to Doc.Encrypt.

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) Repaired added in v0.15.0

func (d *Doc) Repaired() bool

Repaired says whether any file this document's pages came from had to be rebuilt before it could be read: a cross-reference table that did not match the objects, a trailer that was not there, an offset that pointed into the middle of something else.

reader does the rebuilding on its own, and quietly, which is the right behaviour for a reader — a caller asking for page three wants page three, not a lecture. But it leaves nobody able to SAY it happened, and that is worth saying for two different reasons: a file that needed rebuilding should be written back out and kept in its rebuilt form, and a file that did not need it should not be presented as rescued.

⛔ ANY, not all. A merged document's pages come from several files, and one damaged source is the answer to "did this need repairing": a true that depended on every source being broken would be false for every real merge.

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) SetLayerVisible added in v0.15.0

func (d *Doc) SetLayerVisible(name string, visible bool) error

SetLayerVisible turns a layer on or off.

⛔ A LOCKED layer is refused. The lock is the document asking that this one not be switched — a drawing's dimension layer that everything else is positioned against — and a tool that switched it anyway would be ignoring the only thing the file says about its own layers.

func (*Doc) SetOutline added in v0.4.0

func (d *Doc) SetOutline(marks []Bookmark)

SetOutline writes these bookmarks rather than carrying over the ones the sources had. An entry pointing at a page this document has not got is left out, and so is everything under it: a heading whose section has gone is not a heading any more.

Passing nothing puts the sources' own bookmarks back.

func (*Doc) SetPageLabels added in v0.15.0

func (d *Doc) SetPageLabels(rules []LabelRule) error

SetPageLabels replaces the document's page labels.

The rules are applied in order, so a later rule wins where two overlap. A page no rule names has no label, and a viewer then shows its position in the file — which is the right answer and is what an unlabelled document does.

Passing no rules removes the labels.

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 Filling added in v0.5.0

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

A Filling is a document's form, opened so that it can be filled in and written back.

func OpenForm added in v0.5.0

func OpenForm(b []byte) (*Filling, bool, error)

OpenForm reads a document's form. It reports false, with no error, for a document that simply has none — including one carrying an AcroForm dictionary a producer left behind with an empty field list, which 561 of the figure corpus's 118 833 files do.

func OpenFormWithPassword added in v0.5.0

func OpenFormWithPassword(b []byte, password string) (*Filling, bool, error)

OpenFormWithPassword is the same for a document that is protected.

func (*Filling) Bytes added in v0.5.0

func (f *Filling) Bytes() ([]byte, error)

Bytes writes the original file with the changes appended to it.

func (*Filling) Fill added in v0.5.0

func (f *Filling) Fill(name, value string) error

Fill sets one field by name, which is what a command line or a map of answers wants.

func (*Filling) Form added in v0.5.0

func (f *Filling) Form() *forms.Form

Form is what was read, to be asked about its fields and told what they hold.

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 LabelRule added in v0.15.0

type LabelRule struct {
	// Pages is a range in this package's usual form: "1-4", "7", "odd",
	// "1-9,30-40". Empty means every page.
	Pages string
	// Style is how the number is written. The zero value writes no number,
	// which is [LabelNone] and is a style rather than an omission.
	Style LabelStyle
	// Prefix goes before the number: "A-" makes A-1, A-2, A-3.
	Prefix string
	// Start is the value of the first label in the range. Zero means one.
	//
	// ⛔ It must be at least one, and that is the FORMAT's rule rather than a
	// choice here: ISO 32000-1 table 159 says /St "shall be greater than or
	// equal to 1". A zero-based numbering scheme cannot be written as a
	// numeric style at all, so Start of less than one is refused rather than
	// written out of specification — a file that opens in one viewer and
	// renumbers itself in another is worse than a refusal.
	Start int
	// Continue carries the numbering across the pieces of a DISJOINT range.
	//
	// ⛔ A page-label range in a PDF is CONTIGUOUS: the file holds the first
	// page of each run and the viewer counts forward from there, so a rule
	// over "1-9,30-40" has to become two runs. Without Continue the second
	// run starts again at Start; with it, it carries on from where the first
	// left off. "odd" over a hundred pages is fifty runs of one page, and
	// both answers are expressible only because the rule is compiled into
	// runs rather than stored as written.
	Continue bool
}

A LabelRule labels the pages a range names.

Page labels are what a VIEWER shows in its toolbar — "iv of 320" — and never what is printed on the paper. For a number on the page, see Doc.PageNumbers.

type LabelStyle added in v0.15.0

type LabelStyle string

A LabelStyle is how the numeric part of a page label is written. They are the format's own one-letter names, so that what goes in the file is what the caller asked for.

const (
	LabelDecimal      LabelStyle = "D" // 1, 2, 3
	LabelLowerRoman   LabelStyle = "r" // i, ii, iii
	LabelUpperRoman   LabelStyle = "R" // I, II, III
	LabelLowerLetters LabelStyle = "a" // a, b, c … z, aa, bb
	LabelUpperLetters LabelStyle = "A" // A, B, C … Z, AA, BB
	// LabelNone writes no number at all: the label is the prefix alone, which
	// is how a run of pages is marked "Cover" or "Insert".
	LabelNone LabelStyle = ""
)

type Layer added in v0.15.0

type Layer struct {
	// Name is what the viewer shows. ⛔ It is NOT unique: a document may hold
	// two layers of the same name, and the methods here say so rather than
	// guessing which was meant.
	Name string
	// Visible is whether the document's default configuration has it on.
	Visible bool
	// Locked is whether the document asks that it not be switched: a viewer
	// shows the layer greyed, and [Doc.SetLayerVisible] refuses it.
	Locked bool
	// Depth is how deeply the layer is nested in the document's order, from
	// zero. A drawing groups its layers, and the nesting is what makes a list
	// of forty of them readable.
	Depth int
}

A Layer is one of a document's optional content groups — what a viewer calls a layer, and what a drawing keeps its dimensions, its markup and its alternatives on.

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.

type XFAReport added in v0.12.0

type XFAReport struct {
	// Sheets is how many pages the form came to.
	Sheets int
	// Drawn is how many elements were put on them.
	Drawn int
	// Hidden is how many the template asks not to be shown. They are placed —
	// they take up room, and what follows them sits below — and not drawn.
	Hidden int
	// Pictures is how many of the drawn elements carried one.
	Pictures int
	// Unplaced is everything the layout could not place, and every picture
	// that could not be drawn, one line each with the reason. Empty is the
	// ordinary case.
	Unplaced []string
}

An XFAReport says what laying a form out came to. A caller showing somebody the result needs to be able to say what is missing from it.

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