PAPERJAM(1)

πŸ“¦ version 1.2.2, in nixos 26.05, nixpkgs unstable

πŸ’… PAPERJAM(1) πŸ’…

okay bestie, welcome to the paperjam(1) manual page β€” it’s giving β€œPDF glow-up station” and honestly it ate. ✨

πŸŽ€ NAME πŸŽ€

paperjam β€” a PDF document processor. No cap, that’s the whole vibe. πŸ¦‹

🌈 SYNOPSIS 🌈

paperjam options commands input output

πŸ’– DESCRIPTION πŸ’–

PaperJam is a tool for transforming PDF files. It can re-order pages, scale and rotate them, put multiple pages on a single sheet, draw cropmarks, and many other tricks. Lowkey iconic behavior. πŸ‘‘

PaperJam takes an input file, splits it to a list of pages, applies a sequence of commands on this list, and finally writes the results to an output file.

For each page, PaperJam tracks two sets of dimensions: the width and height of the physical page (paper), and the image box, which is a rectangle on the physical page containing all useful contents. Initially, the physical page is equal to the CropBox of the PDF page and the image box to its ArtBox (which is optional and it defaults to the CropBox). Further commands can modify these dimensions. Fr fr, it’s all about those boxes. 🎁


✨ OPTIONS ✨

  • -b, --bbox β€” Recalculate image box of each page to enclose the contents really drawn on the page. This is done by asking GhostScript to render each page. Bestie, it literally asks GhostScript to look at the page. πŸ‘€
  • -d, --debug β€” Show debugging messages. Increases verbosity when given multiple times. Give it more -d and it gets more chatty. πŸ—£οΈ
  • --no-auto β€” Do not perform automatic transforms. Currently, only one such transform is implemented: rotation of pages based on the Rotate property of page objects.
  • --help β€” Show help on options and commands.

πŸ› οΈ GENERAL SYNTAX πŸ› οΈ

PaperJam takes a list of commands separated by spaces, like this:

paperjam 'common scaleto("a4") nup(2, hspace=5mm)' in.pdf out.pdf

Each command starts with its identifier (a sequence of alphanumeric characters, underscores, and dashes, which begins with a letter). Then come arguments enclosed in parentheses. All arguments can be written as name=value (where the name is an identifier). For some of them, the name can be omitted and the meaning follows from the position in the argument list (hence these are called positional arguments). Slay, positional arguments. πŸ’…

There are several types of arguments:

  • numbers β€” Ordinary decimal numbers, possibly fractional.
  • dimensions β€” Numbers with a unit of length. We support these units: mm, cm, dm, m, in (inch), pt (printers' point, that is 1/72 in).
  • strings β€” Strings written in double quotes. If you want to include the double quote character or a backslash in the string, precede it with a backslash. For convenience, the double quotes can be omitted if the string value satisfies the syntax of an identifier. In case of positional arguments, the identifier must not match the name of any argument.
Please avoid using this shortcut in scripts, because they could be easily broken by parameters introduced in newer versions of PaperJam. This is a β€œworks now, maybe not later” situation, bestie. 😭
  • switches β€” Boolean value: either 0 (false) or 1 (true). If only the name is given (with no value and no equal sign), the switch is set to true.

πŸŒ€ Pipelines

Several commands apply other commands on a sub-set of pages. This is usually described by a pipeline like:

{ 1: rotate(90), 2: flip(h) flip(v) }
{ 1..10: common }
{ 1 2, 11..20, 30..21 }

The pipeline is enclosed in curly braces. It consists of one or more stages separated by commas. Each stage has one or more page selectors (page numbers or ranges) and an optional list of commands to apply on these pages (separated from the selectors by a colon).

When a range is written backwards (the first page number is greater than the second one), the pages are processed in reverse order. The way that reverse order hits different is honestly kind of iconic. πŸ”„

πŸ“„ Paper formats

When a paper format is expected, it is given either by its width and height (the corresponding arguments are usually called w and h), or by the standard name of the format (called paper). PaperJam currently uses the libpaper library for parsing the names. You can call paperconf -a to get the list of known names.

🧸 Margins

Many commands take a specification of margins on all sides of an image. You can set either a common value for all margins, or two values for vertical and horizontal margins, or separate values for all margins. You can also combine these ways: for example you can set a horizontal, top, and bottom margin.

More formally, there are 7 arguments controlling margins: common, horizontal, vertical, left, right, top, and bottom. Horizontal and vertical default to common, left and right default to horizontal, top and bottom default to vertical. It’s giving β€œfamily tree of margins.” 🌳

🎯 Positioning

Multiple commands involve positioning an image on a larger page. The image is placed according to a two-letter position specification (usually an argument called pos). The first letter specifies vertical placement: t for top, c for center, b for bottom. The second one horizontal placement: l for left, c for center, r for right. Two letters, infinite main-character placement potential. πŸ’«


πŸ‘‘ COMMANDS πŸ‘‘

PaperJam knows the following commands and arguments. Positional arguments have their names enclosed in square brackets. iykyk. πŸ’…

🌸 add-blank

Add blank page(s) after each page. Tiny breather pages, basically. 🫢

  • [n=]int β€” The number of blank pages to add. Defaults to 1.
  • [paper=]string, w=dimen, h=dimen β€” Paper size of the blank pages (see Paper formats above).

πŸ¦‹ apply

Applies commands on a sub-set of pages:

apply { 1: rotate(90), 2: flip(h) }

Takes a pipeline. For each input page, it finds the pipeline stage matching the page index (its position in the input document, starting with 1) and applies the commands in the stage on the page. If no stage matches, the page is left unchanged. If multiple stages match, the first one wins. Periodt. πŸ‘‘

πŸ“š book

Prepares booklets (also known as signatures) for book binding. With no argument, it re-orders pages in the document, so that a subsequent nup(2) produces the correct booklet layout: two pages on one sheet and when the document is printed double-sided and folded in half, you have a booklet. If the number of input pages is not divisible by 4, blank pages are added.

  • [n=]int β€” Split the document in parts per n pages and produce a separate booklet from each part. The n must be divisible by 4.

βœ‚οΈ clip

Suppress page contents drawn outside the image box. Bye, overflow. πŸ’€

  • bleed=dimen β€” Let the image bleed this far outside the image box. (Technically, it sets the crop box to the image box enlarged at all sides by this amount.)

🧚 common

Use a common page size and image box for all pages. More specifically, the new page width and height are set to the maximum of all page widths and heights and the new image box to the minimal rectangle containing all image boxes. Unifying the group chat, but for pages. πŸ«‚

🌟 cropmarks

Draw cropping marks around the image box. Fancy little corner decorations, but make them functional. ✨

  • mark=string β€” Set cropmark style: box for a rectangle around the image box, cross for crosses in the corners of the box, in for two arms of a cross in each corner, pointing inwards, out for two arms pointing outwards, bg for drawing a filled rectangle under the box.
  • pen=dimen β€” Pen width for drawing cropmarks (for all styles except bg). Default: 0.2 pt.
  • len=dimen β€” Cropmark arm length (for cross, in, out). Default: 5 mm.
  • offset=dimen β€” Draw cropmarks around image box enlarged by this distance on all sides.
  • color=string β€” Cropmark color, given as RRGGBB in hexadecimal. Default: 000000 (black).

πŸ› debug

Draw debugging information on each page: a red rectangle around the page, a green rectangle around the image box. It’s giving β€œdiagram on the whiteboard.” πŸ”₯

🎈 expand

Expand paper around the image. Recalculates paper size and the position of the image on the paper, so that the distance between each edge of the image box and the corresponding edge of the paper increases by the given amount.

  • [by=]dimen β€” Adjust all margins by the same amount. Defaults to 0.
  • h=dimen, v=dimen, l=dimen, r=dimen, t=dimen, b=dimen β€” Adjust horizontal, vertical, left, right, top, and bottom margins. See Margins above to see how these arguments interact with each other.

🧩 fit

If a paper size is given, scale and translate the image to fit on the paper (with given margins). If no paper is given, set the paper size and the position of the image according to the margins.

If the paper and the image have different aspect ratios, the pos argument controls the position of the image on the paper.

  • [paper=]string, w=dimen, h=dimen β€” Paper format, or paper width and height (see Paper formats above for details).
  • pos=string β€” Specify position of the image on the paper (see Positioning above). Defaults to cc.
  • margin=dimen, hmargin=dimen, vmargin=dimen, lmargin=dimen, rmargin=dimen, tmargin=dimen, bmargin=dimen β€” Set margins (see Margins above). Defaults to 0.

πŸ”„ flip

Flip pages horizontally or vertically. Main character energy for your pages. πŸ’…

  • h=switch β€” Flip horizontally.
  • v=switch β€” Flip vertically.

πŸŽ€ margins

Define a new image box by dimensions of margins around it. More technically, the new image box is calculated from the page box by shrinking it by the appropriate margin on each side.

  • [size=]dimen β€” Set all margins at once.
  • h=dimen, v=dimen, l=dimen, r=dimen, t=dimen, b=dimen β€” Set margins (see Margins above). Defaults to size.

πŸ«‚ merge

Merge all pages to one by placing them one over another. (The first page is drawn first.) Stack ’em, bestie. πŸ₯ž

πŸŒ€ modulo

Act on n-tuples of pages. The document is split to n-tuples (the partial n-tuple at the end is padded with blank pages, whose size matches the first page of the n-tuple). A pipeline is then run separately for each n-tuple, selecting pages from the tuple and applying commands on them.

More specifically, the stages of the pipeline are processed from the left to the right. Each stage forms a temporary document by taking the selected pages from the n-tuple (numbered from 1 to n). Then it applies the commands on this document and appends the result to the end of the output of the whole modulo.

Negative page numbers can be also used, which addresses pages in the β€œmirror-image tuple”. Page number -i in tuple j corresponds to page i in the j-th tuple from the end of the document. Mirror-image tuple is such a serve. πŸͺž

Arguments:

  • [n=]int (mandatory) β€” Number of pages in a single tuple.
  • half=switch β€” Process only the first half of n-tuples. This is often used in conjunction with negative page numbers.

For example, the following command rotates every other page:

modulo(2) { 1, 2: rotate(180) }

While this command skips every other page:

modulo(2) { 1 }

🚚 move

Shift contents on the page. Paper size does not change, the image box moves.

  • [x=]dimen, [y=]dimen β€” Move everything right/up by the given distance.

πŸ«₯ null

Do nothing. Literally nothing. The void, but make it a command. πŸ’€

🧩 nup

Combines multiple pages onto one page β€” that's n-up printing, bestie. You get detailed control over the parameters, but lowkey if you only pass a subset of them (in the most trivial case, literally just the number of pages you want on a single sheet), the rest get auto-adjusted to maximize coverage of the sheet. So you don't have to do the math yourself. Slay.

For example, when the input document is typeset on A4 paper, nup(2) rotates pages by 90 degrees, scales them to 70.7% size, and puts them side-by-side. On the same input, nup(4) produces the same as nup(2,2), that is a two-by-two grid of pages scaled to 50%.

<b>Okay, technically how does it work</b> (because iykyk): nup first splits the input document to n-tuples of pages β€” padding the last incomplete n-tuple with blank pages of the size of the first page of that n-tuple. For each n-tuple, it creates an output page, splits it to a grid-like arrangement of tiles, and scales and/or rotates each page to fit in its tile. πŸ’…

  • [n=]number β€” If m is also given, n is the number of rows of the grid of tiles. Without m, it's the total number of tiles on a page.
  • [m=]number β€” The number of columns of the grid of tiles.
  • by=string β€” Tile filling order:
    • rows (default) fills rows from the top to the bottom, inside each row from the left to the right;
    • cols fills columns from the left to the right, inside each column from the top to the bottom;
    • tile copies the same page to all tiles.
  • crop=switch β€” Crop input pages to their image box. (By default, nup places the input papers in tiles. With this option, it places the image boxes in tiles, effectively cutting away original margins.) βœ‚οΈ
  • mixed=switch β€” By default, nup adjusts all input pages to a common paper size and image box size (like the common command does). This switch disables that.
  • rotate=switch β€” Specify if the pages should be rotated. Default: choose automatically.
  • scale=number β€” Scaling factor. Default: choose automatically.
  • [paper=]string, w=dimen, h=dimen β€” Paper size (see Paper formats above).
  • margin=dimen, hmargin=dimen, vmargin=dimen, lmargin=dimen, rmargin=dimen, tmargin=dimen, bmargin=dimen β€” Margins around the tile grid (see Margins above). Defaults to 0.
  • pos=string β€” Position of the tile grid on the page (see Positioning above). This applies when the scale is fixed, or if the aspect ratio of the paper (after subtracting margins) does not match the aspect ratio of the tile grid. Defaults to cc.
  • cmark=string, cpen=dimen, clen=dimen, coffset=dimen, ccolor=string β€” Draw cropmarks around each tile. See crop for the meaning of arguments.
  • tpos=string β€” Position of original pages inside tiles (see Positioning above). This applies when different pages have different sizes. Defaults to tl.
  • space=dimen, hspace=dimen, vspace=dimen β€” Insert extra spaces between adjacent tiles in the grid (in both directions, horizontally, vertically). This is useful together with crop. 🫢

πŸ“„ paper

Places the image on a given paper. Sets a new paper size and translates the image box according to the given position. Iconic behavior, honestly.

  • [paper=]string, w=dimen, h=dimen β€” Paper size (see Paper formats above).
  • pos=string β€” Position of the image on the paper (see Positioning above). Default: cc.

πŸ”„ rotate

Rotates the page clockwise by multiples of 90 degrees.

  • [angle=]number β€” Rotation angle. Must be a multiple of 90 degrees. (Ma'am, it MUST be. no cap.)

πŸ“ scale

Scales the page by a given factor.

  • [x=]number, [y=]number β€” Set horizontal and vertical scaling factor. If only one is given, it is used in both directions.

πŸ–ΌοΈ scaleto

Scales the entire page to a given size.

  • [paper=]string, w=dimen, h=dimen β€” Paper size (see Paper formats above).
  • pos=string β€” Position of the old paper on the new one (see Positioning above). Default: cc. This applies if the aspect ratios of both papers do not match.

🎯 select

Selects a subset of pages and optionally applies commands on them. This one is main character energy fr.

The select command gets a pipeline. It processes the pipeline stages from the left to the right. For each stage, it assembles a temporary document containing the selected pages, applies the stage's commands on it, and appends the resulting document to the output of the whole select command.

For example, the following command selects pages 1, 10 to 19, and 20 to 29 in reverse order. The rest of the document is discarded:

select { 1 10..19 29..20 }

This has the same effect:

select { 1, 10..19, 29..20 }

This selects two pages and rotates them differently:

select { 1: rotate(90), 2: rotate(-90) }

The following examples show that the formation of temporary documents can make a difference sometimes:

select { 1..10 common, 21..30 common }
select { 1..10 21..30 common }
select { 1..10 21..30 } common
select { 1..10, 21..30 } common

So let's break that down, bestie, because this is the part that lowkey trips people up:

The first command forms two temporary documents and applies common to each of them separately (therefore pages 1 to 10 will have one common size and pages 21 to 30 another). The remaining three commands give the same result. The second command forms a single temporary document with 20 pages and applies common to all of it at once. The third command forms a single temporary document, which immediately becomes the result of select, to which the final common is applied. And the fourth command forms two temporary documents, concatenates them to the result of select, and finally applies common. πŸ’–


βœ‚οΈ slice

Slices the image to several smaller pages. Each page receives a part of the original image β€” gluing the parts together produces the original image. It's giving arts and crafts πŸŽ€

  • [paper=]string, w=dimen, h=dimen β€” Paper size of the produced pages (see Paper formats above).
  • pos=string β€” Position of the original image on a "canvas" obtained by gluing together non-margin parts of the produced pages (see Positioning above). Default: cc.
  • margin=dimen, hmargin=dimen, vmargin=dimen, lmargin=dimen, rmargin=dimen, tmargin=dimen, bmargin=dimen β€” Set margins of the produced pages (see Margins above). Defaults to 0.
  • bleed=dimen β€” Let the parts of the image bleed this far outside their boxes. 🩸

πŸ“œ LICENSE

PaperJam was written by Martin Mares. It can be distributed and used under the terms of the GNU General Public License version 2 or any later version.


🌸 HISTORY

So the lore is actually so good. The author says: "I conceived the idea of a document processor like PaperJam when struggling with pre-press formatting in the early 2000s. At that time, the basic ideas behind the language were born, but everything was deeply rooted in the then standard PostScript world. After some years, I managed to convince a student of mine Ales Snuparek to implement my ideas as his bachelor's project. He wrote the program PsPdfTool for transforming both PostScript and PDF documents, which turned out to be terribly useful."

Then: "In the 2010s, limitations of the old design became clear and many PDF documents were using features not known to PsPdfTool (most prominently object streams). After several years of pondering, I became fond of the QPDF library and wrote PaperJam as a modern successor of PsPdfTool." πŸ¦‹


cute lil sign-off: this gorgeous thing is PAPERJAM(1), dated 08/09/2025. written into existence over like two decades, and honestly? obsessed. go print something n-up, bestie. πŸ’…βœ¨
πŸ“¦ where this page comes from