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)
| File | Size | Uploaded | |
|---|---|---|---|
| pyfcomp-0.1.1.tar.gz | 76.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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