aboutsummaryrefslogtreecommitdiff
path: root/README
diff options
context:
space:
mode:
Diffstat (limited to 'README')
-rw-r--r--README156
1 files changed, 156 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..c2d54d5
--- /dev/null
+++ b/README
@@ -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.