Skip to main content

pyfcomp

pyfcomp validates a completed image or PDF form against a trusted basis form. You declare the meaningful rectangular regions and whether each one must change, may change, or must not change. All differences outside declared regions are ignored.

It accepts paths or document bytes, handles multi-page PDFs, and returns structured per-page and per-region diagnostics.

Installation

pip install pyfcomp

Python 3.10 or later is required.

Quick start

from pyfcomp import ChangeRule, FormRules, Region, validate_forms

rules = FormRules(
    (
        # A signature is required here.
        Region("signature", 0, 0.10, 0.70, 0.30, 0.12, ChangeRule.MUST_CHANGE),
        # This pre-printed value must not be altered.
        Region(
            "account_number",
            0,
            0.55,
            0.20,
            0.25,
            0.06,
            ChangeRule.MUST_NOT_CHANGE,
        ),
    )
)

result = validate_forms("basis.pdf", "candidate.pdf", rules)

if result.valid:
    print("The candidate is valid.")
else:
    print(result.issues)
    for page in result.page_results:
        for region in page.regions:
            if not region.valid:
                print(f"{region.region.name}: {region.changed_ratio:.1%} changed")

Inputs may be str or pathlib.Path filenames, or raw document bytes. Raster images are one-page documents. PDFs are rendered page by page, and the basis and candidate must have equal page counts.

Extract form fields

Use extract_form_fields() to turn a fillable PDF's widget locations into the same zero-based, normalized, top-left-origin coordinates used by Region:

from pyfcomp import extract_form_fields

for field in extract_form_fields("form.pdf"):
    print(field.name, field.page, field.x, field.y, field.width, field.height)

The extractor uses a PDF's native AcroForm widgets when they are present, so their coordinates are exact. name uses the nearby printed label when one is available and source_name preserves the PDF's internal field name. A flat PDF has no widgets; it is rendered and processed with Tesseract OCR automatically.

OCR dependency

Flat-PDF extraction requires the Tesseract OCR executable to be installed and available on PATH. The Python package declares its pytesseract binding as a dependency, but it cannot install the Tesseract executable itself. For example, install it with brew install tesseract on macOS, or your system package manager on Linux.

If you use an existing editable checkout in Jupyter, install the updated Python dependencies into that notebook's kernel with %pip install -e ., then restart the kernel.

OCR returns field candidates from recognized text and their bounding boxes. Use an image_detector for a form-specific OCR/vision integration when you need more precise input-area detection or custom naming.

Raster images have no embedded field names or rectangles. Supply an OCR/vision adapter with image_detector; it receives an RGB Pillow image and page 0, and returns FormField values in normalized coordinates:

from pyfcomp import FormField, extract_form_fields


def detect(image, page):
    # Call the OCR/vision service of your choice, then normalize its pixel box.
    return [FormField("signature", page, 0.10, 0.70, 0.30, 0.12)]


fields = extract_form_fields("scanned-form.png", image_detector=detect)

# Override the built-in Tesseract fallback for a flat PDF.
fields = extract_form_fields("flat-form.pdf", image_detector=detect)

Define regions

Region(name, page, x, y, width, height, rule)

page is zero-based. The rectangle values are normalized from 0 to 1, with the origin at the top-left of the page. Therefore x=0.5 begins at the horizontal midpoint regardless of a page's physical size or image resolution.

Rule Meaning
ChangeRule.MUST_CHANGE Invalid unless the region changed enough.
ChangeRule.MAY_CHANGE Always valid; metrics are still reported.
ChangeRule.MUST_NOT_CHANGE Invalid if the region changed enough.

Regions may overlap; each is evaluated independently.

Load JSON rules

Store rules in JSON when they should be configured outside Python:

{
  "regions": [
    {
      "name": "signature",
      "page": 0,
      "x": 0.1,
      "y": 0.7,
      "width": 0.3,
      "height": 0.12,
      "rule": "must_change"
    }
  ]
}
from pyfcomp import FormRules, validate_forms

rules = FormRules.from_json_file("rules.json")
result = validate_forms("basis.pdf", "candidate.pdf", rules)

Use FormRules.from_json(...) for a JSON string or bytes, and to_json() to write Python rules in the same format.

Tune comparison

The default comparison renders PDFs at 200 DPI. A region counts as changed when at least 1% of its pixels differ beyond a visual per-pixel tolerance. Configure these values for your documents:

from pyfcomp import ValidationOptions

options = ValidationOptions(
    dpi=200,
    auto_align=True,
    max_translation_ratio=0.02,
    pixel_threshold=0.1,
    changed_pixel_ratio=0.01,
)

result = validate_forms("basis.pdf", "candidate.pdf", rules, options=options)

Candidate pages are resized to the basis raster dimensions. Auto-alignment is enabled by default to correct small x/y scan or print shifts; set auto_align=False to require exact placement. Rotation, scale, and perspective correction are not supported.

Results and diagnostics

ValidationResult provides the overall valid flag, issues, and a PageResult for every matched page. Each RegionResult contains its validity, changed and total pixel counts, changed ratio, and difference bounding box.

Request an in-memory Pillow overlay for every compared region:

result = validate_forms("basis.pdf", "candidate.pdf", rules, include_diagnostics=True)

for page in result.page_results:
    for region in page.regions:
        if region.diagnostic is not None:
            region.diagnostic.save(f"{region.region.name}-diff.png")

The library does not write files unless your application saves a diagnostic.

Errors

Invalid rules raise InvalidRulesError. Unreadable or unsupported inputs raise UnsupportedDocumentError. A page-count mismatch returns an invalid result so batch jobs can report it as a normal validation failure.

Development

uv sync --all-groups
uv run pytest --cov=pyfcomp --cov-report=term-missing
uv run ruff check .
uv run ruff format --check .
uv run ty check

Metadata

Release files for pyfcomp 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyfcomp 0.1.1
File Size Uploaded
pyfcomp-0.1.1.tar.gz 76.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyfcomp 0.1.1
File Interpreter ABI Platform
pyfcomp-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 89.9 kB

Release files / pyfcomp-0.1.1.tar.gz

Download URL pyfcomp-0.1.1.tar.gz
Size 76.0 kB
Tags Source
SHA-256 checksum
How to use checksums
b0d7a9594fcb32a53ef8e8e7bed8a36be167376d263daf19c2a03dfd8edf17ca
BLAKE2b-256 checksum
How to use checksums
0f71a6b959ebccf4032fb098e407b89ce7ce913aefdaa8c5fe0dccb366eb5c1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfcomp-0.1.1-py3-none-any.whl

Download URL pyfcomp-0.1.1-py3-none-any.whl
Size 14.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
705b66e926ec11b8223e3ed0d2e7c6cb2734b4284d1317b580dd8982049e9c0e
BLAKE2b-256 checksum
How to use checksums
b5c2eb2c57bc7eb2ecfcf8aa494d1c21e2732e0640b40bababe021bf186d4b6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page