diff options
| author | Lena <lena@omega> | 2026-04-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-04-01 00:00:00 +0000 |
| commit | 459f5935aab505cdb8174e5f9f04a9a3c426f37e (patch) | |
| tree | 525a5b39b05ef2c799b953ad3969e869ab484745 /README | |
| download | pdfstamp-master.tar.gz | |
Terminal-only PDF stamp picker. Renders a coarse page preview in the
terminal, places text and image stamps with the keyboard, saves the
layout as a JSON plan, and burns it into an output PDF.
MuPDF (via go-fitz) renders the preview; pdfcpu overlays the generated
stamp PDF onto the source 1:1 and validates the result.
Diffstat (limited to 'README')
| -rw-r--r-- | README | 156 |
1 files changed, 156 insertions, 0 deletions
@@ -0,0 +1,156 @@ +pdfstamp +======== +pdfstamp is a terminal-only PDF stamp-region picker. It renders a coarse, +content-aware page preview directly in the terminal and lets you place text and +image stamps with the keyboard, save the layout as a JSON plan, and burn the +stamps into an output PDF. + +It does coarse placement, not pixel-perfect editing. Zoom in for finer control +over stamps, initials, signatures, dates, and labels. + + +Build +----- +pdfstamp uses MuPDF (via go-fitz) for rendering, which requires cgo. go-fitz +bundles musl-built MuPDF static libraries (no system PDF library is needed), +and the build links fully static: the one binary has no libc dependency and +runs on both glibc and musl machines. + +On Alpine: + + apk add build-base go + make build + +On Debian, install the musl toolchain and select it explicitly: + + apt install build-essential musl-tools + make build CC=musl-gcc + +There is no native Windows build; on Windows, run the same static binary +under WSL. + + +Usage +----- + pdfstamp pick INPUT.pdf [-o PLAN.json] place stamps; write the plan + pdfstamp apply PLAN.json OUTPUT.pdf burn a plan into an output PDF + pdfstamp INPUT.pdf OUTPUT.pdf pick then apply in one step + +With no -o, pick writes the plan to stdout. apply reads the plan from stdin when +the path is -, so the two steps compose as a pipe: + + pdfstamp pick INPUT.pdf | pdfstamp apply - OUTPUT.pdf + +The picker draws to /dev/tty, so stdout carries only the plan. apply reads the +source PDF path from the plan's "input" field. A sample document ships with the +repo; try: + + pdfstamp pick example.pdf -o plan.json + pdfstamp apply plan.json stamped.pdf + + +Controls +-------- +The picker is modal on selection: with no stamp selected, h/j/k/l move the +cursor; with a stamp selected (tab), they move the stamp. + + n / p next / previous page + g go to page + h j k l move cursor (or selected stamp) + arrows move (same as h j k l) + H J K L resize selected stamp + space start / finish a rectangle, then add a stamp in it + a add a stamp at the cursor (or edit the selected one) + d delete the selected stamp + P set the selected stamp's pages (e.g. 1,3-5 or all) + o set the selected stamp's opacity (0-1) + tab cycle stamp selection (and back to cursor mode) + + / - zoom in / out + enter save the plan (pick), or apply and quit (one-step) + ? toggle help + q quit + +A truecolor terminal (COLORTERM=truecolor) gets a colored half-block preview; +otherwise a grayscale ASCII preview is used. tmux and screen usually strip +COLORTERM; set it inside the multiplexer to keep the colored preview. + + +Plan format +----------- +The plan is plain JSON. Coordinates are PDF points with a bottom-left origin. +Pages are 1-based. Text uses Helvetica, sized to fit the rectangle (no per-stamp +font/size/color). Only printable ASCII is supported, as no font is embedded; +plans with other characters are rejected. +Images (PNG or JPEG) are scaled to fit the rectangle while preserving aspect +ratio, anchored at the bottom-left corner. + +pick writes absolute paths so a saved plan applies from any directory. In a +hand-written plan, relative "input" and "src" paths are resolved relative to the +plan file's directory (or the working directory when the plan is read from +stdin). + + { + "version": 2, + "input": "input.pdf", + "stamps": [ + { "id": "stamp-1", "type": "text", "content": "APPROVED", + "pages": [1], "rect": { "x": 420, "y": 610, "w": 120, "h": 42 }, + "opacity": 0.8 }, + { "id": "stamp-2", "type": "image", "src": "sig.png", + "pages": [1, 3], "rect": { "x": 100, "y": 90, "w": 80, "h": 40 }, + "opacity": 1.0 } + ] + } + + +How it works +------------ +pick rasterizes each page with MuPDF and draws it as Unicode half-blocks, one +character per two vertical pixels, mapping terminal cells to PDF points. + +apply builds an in-memory overlay PDF with one page per source page, drawing +each stamp at its exact point coordinates (text via a base-14 Helvetica content +stream, images as RGB XObjects with a soft-mask for transparency). pdfcpu then +overlays that PDF onto the source 1:1 (multi-stamp, centered, scale 1, no +rotation) and validates the result. Because the overlay pages share the source +MediaBoxes, the overlay is an identity transform. On pages with /Rotate, the +overlay uses the viewed page size and pdfcpu counter-rotates it, so stamps land +where the picker showed them. + + +Debugging +--------- +The overlay content streams are uncompressed and human-readable; dump the +overlay or output with a viewer or `strings` to inspect the drawing operators +(`cm`, `Tj`, `Do`). Run the integration test, which applies a known plan and +asserts that stamps land in the expected pixel regions: + + make test + +On Debian, use the same musl compiler override as the build: + + make test CC=musl-gcc + +If stamps appear rotated or offset, check the watermark fields set in apply.go: +pdfcpu defaults to a diagonal placement, so Rotation 0 and NoDiagonal must both +be set to get a straight 1:1 overlay. + + +Exit status +----------- +0 on success, 1 on runtime errors, 2 on usage errors. The picker exits 130 on +SIGINT and 143 on SIGTERM, after restoring the terminal. + + +Limitations +----------- +apply refuses, rather than silently misplacing stamps, on pages it cannot map +1:1: pages whose MediaBox/CropBox origin is not at (0,0). Text stamps are +printable ASCII only (no embedded font). Each stamp image is capped at 50 +megapixels, with a 100-megapixel total across distinct images. + + +License +------- +pdfstamp links MuPDF (AGPL-3.0). Distributing the binary therefore puts it under +the AGPL-3.0 unless you hold a commercial MuPDF license. pdfcpu is Apache-2.0. |