Skip to content

pillarsdotnet/make_fillable

Repository files navigation

make_fillable

Convert a scanned form PDF into a fillable AcroForm PDF.

make_fillable rasterises each page, uses OpenCV to find the places a human would write, and injects matching interactive form fields back into the original PDF with pikepdf. The page imagery is left untouched — only AcroForm widgets are added — so the result looks identical on paper but can be filled in and printed from any PDF viewer.

What it detects

Feature on the page Field created
Bordered table grids One text field per empty cell (header/label cells are skipped)
Horizontal underlines (blank write-on lines) A single text field sitting just above the line
Date blanks ___/___/___ Three separate fields (month / day / year) that don't cover the slashes
Small hollow squares Checkbox fields

It also tries hard to avoid false positives: full-width decorative rules, underlined instruction text, and a grid's own ruling lines are ignored, and fields are sized to leave room for descenders so printed text clears the line.

Requirements

  • Python 3.9+
  • poppler-utils — the pdftoppm command is used to rasterise pages and must be on your PATH. This is a system dependency, not a pip package:
    • Debian/Ubuntu: sudo apt install poppler-utils
    • Fedora: sudo dnf install poppler-utils
    • macOS (Homebrew): brew install poppler
  • Python dependencies (installed automatically by pip): opencv-python-headless, numpy, pikepdf.

Installation

With pipx (recommended on Ubuntu/Debian)

pipx installs the command into its own isolated environment and puts make_fillable on your PATH — and it works on modern Debian/Ubuntu where the system Python is "externally managed" (PEP 668) and a plain pip install into it is refused. From the project directory:

pipx install .

To reinstall / upgrade after changes:

pipx install --force .

(sudo apt install pipx first if you don't have it.)

With pip

From the project directory:

pip install .

Or an editable/development install:

pip install -e .

Note on OpenCV and externally-managed Pythons: the package depends on opencv-python-headless (no GUI libraries). On Debian/Ubuntu the system Python is externally managed, so pip install must target a virtualenv — and a fresh venv will try to download the (large) OpenCV wheel. If your system already provides OpenCV/numpy/pikepdf via apt (python3-opencv, python3-pikepdf, python3-numpy), reuse them by creating the venv from the system interpreter with --system-site-packages and installing with --no-deps (no downloads required):

python3 -m venv --system-site-packages .venv
.venv/bin/pip install --no-deps .

The apt packages must match the interpreter the venv is built from: a venv created by a different Python (e.g. one uv manages) cannot reuse them and will fall back to downloading the wheels.

Usage

make_fillable input.pdf output.pdf

Equivalently, as a module:

python -m make_fillable input.pdf output.pdf

Example output:

Rendering pages from input.pdf …
  Page 1/11 … 0 cell, 33 text, 3 date, 20 checkbox fields
  ...
Saved → output.pdf  (844 fields total)

As a library

from pathlib import Path
from make_fillable import process

process(Path("input.pdf"), Path("output.pdf"))

Individual detection helpers (detect_tables, detect_checkboxes, classify_lines, …) are importable from make_fillable.core if you want to build a custom pipeline.

Tuning

Detection thresholds are module-level constants at the top of src/make_fillable/core.py — e.g. underline width ratios, checkbox size range, minimum field height. They are tuned for 300 DPI Letter-size scans of typical printed forms; adjust them if your documents differ (denser grids, smaller checkboxes, etc.).

How it works

  1. pdftoppm renders every page to a 300 DPI grayscale image.
  2. OpenCV morphology isolates horizontal/vertical rules to reconstruct table grids; connected components of the non-grid area become candidate cells.
  3. Remaining horizontal strokes are filtered into genuine write-on underlines, then classified as plain text or date (___/___/___) fields.
  4. Hollow squares are found via contour hierarchy (an outer box with a large empty hole) and turned into checkboxes.
  5. Image coordinates are mapped back to PDF points and written as AcroForm widget annotations with the Print flag set, so entries appear both on screen and in print.

Notes & limitations

  • Field names encode their page and a running index (text_1_4, date_1_9_month, cell_4_57, cb_3_45), so they are unique and stable for a given input.
  • Filled values render and print reliably from full PDF viewers (Okular, Evince, Firefox, Adobe Reader). Some lightweight viewers honour NeedAppearances inconsistently; if entries don't print, fill and print from one of the above or flatten the form first.
  • Detection is heuristic. Very faint scans, skew, or unusual layouts may need threshold tuning.

Development

The repository ships pre-commit hooks that lint and type-check on every commit:

  • ruff — lints the Python code (src/)
  • mypy — type-checks the Python code (src/)
  • markdownlint — lints the Markdown (README.md)

Enable them once after cloning:

pip install pre-commit        # or: pipx install pre-commit
pre-commit install

Run them against everything on demand:

pre-commit run --all-files

Tool settings live in pyproject.toml ([tool.ruff], [tool.mypy]) and .markdownlint.yaml; hook versions are pinned in .pre-commit-config.yaml and can be bumped with pre-commit autoupdate.

Ruff runs with select = ["ALL"]. Before changing how lint findings are handled, read the linting policy in CONTRIBUTING.md: fix what is fixable, and otherwise suppress per-line with an explanatory # noqa / # type: ignore — never via global or per-file ignores.

License

MIT — see LICENSE.

About

A python script to create a form-fillable PDF from a scanned page.

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages