From f182f25a042ff5629845532faab43f9f79861d0b Mon Sep 17 00:00:00 2001 From: Joe Barrow Date: Sun, 19 Oct 2025 12:47:11 -0400 Subject: [PATCH] add multiline support --- README.md | 1 + commonforms/__main__.py | 6 ++++++ commonforms/inference.py | 3 ++- commonforms/multiline_tools.py | 24 ------------------------ tests/inference_test.py | 13 +++++++++++++ 5 files changed, 22 insertions(+), 25 deletions(-) delete mode 100644 commonforms/multiline_tools.py diff --git a/README.md b/README.md index 5565742..9da9527 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ commonforms | `--image-size` | int | `1600` | Image size for inference | | `--confidence` | float | `0.3` | Confidence threshold for detection | | `--fast` | flag | `False` | If running on a CPU, you can trade off accuracy for speed and run in about half the time | +| `--multiline` | flag | `False` | If you want the detected textboxes to allow multiline inputs | ## CommonForms API diff --git a/commonforms/__main__.py b/commonforms/__main__.py index 5631e22..f66df1a 100644 --- a/commonforms/__main__.py +++ b/commonforms/__main__.py @@ -50,6 +50,11 @@ def main(): action="store_true", help="If running on a CPU, you can use --fast to get a 50% speedup with a small accuracy penalty", ) + parser.add_argument( + "--multiline", + action="store_true", + help="If you want the detected textboxes to allow multiline inputs.", + ) args = parser.parse_args() @@ -63,6 +68,7 @@ def main(): image_size=args.image_size, confidence=args.confidence, fast=args.fast, + multiline=args.multiline, ) diff --git a/commonforms/inference.py b/commonforms/inference.py index 1b2ebaa..7ac5827 100644 --- a/commonforms/inference.py +++ b/commonforms/inference.py @@ -166,6 +166,7 @@ def prepare_form( image_size: int = 1600, confidence: float = 0.3, fast: bool = False, + multiline: bool = False, ): detector = FFDNetDetector(model_or_path, device=device, fast=fast) @@ -187,7 +188,7 @@ def prepare_form( name = f"{widget.widget_type.lower()}_{widget.page}_{i}" if widget.widget_type == "TextBox": - writer.add_text_box(name, page_ix, widget.bounding_box) + writer.add_text_box(name, page_ix, widget.bounding_box, multiline=multiline) elif widget.widget_type == "ChoiceButton": writer.add_checkbox(name, page_ix, widget.bounding_box) elif widget.widget_type == "Signature": diff --git a/commonforms/multiline_tools.py b/commonforms/multiline_tools.py deleted file mode 100644 index 37b2944..0000000 --- a/commonforms/multiline_tools.py +++ /dev/null @@ -1,24 +0,0 @@ -from pypdf import PdfReader, PdfWriter -from pypdf.generic import NameObject, NumberObject - -def enable_multiline_fields(input_pdf: str, output_pdf: str): - """ - Enables multiline text input on all text fields in a fillable PDF. - """ - reader = PdfReader(input_pdf) - writer = PdfWriter() - - for page in reader.pages: - if "/Annots" in page: - for annot in page["/Annots"]: - obj = annot.get_object() - if obj.get("/FT") == NameObject("/Tx"): - current_flags = obj.get("/Ff", NumberObject(0)) - new_flags = int(current_flags) | 4096 # set multiline flag - obj.update({NameObject("/Ff"): NumberObject(new_flags)}) - writer.add_page(page) - - with open(output_pdf, "wb") as f: - writer.write(f) - - print(f"Done! Modified PDF saved as {output_pdf}") \ No newline at end of file diff --git a/tests/inference_test.py b/tests/inference_test.py index 6f78897..62b9474 100644 --- a/tests/inference_test.py +++ b/tests/inference_test.py @@ -30,6 +30,19 @@ def test_inference_fast(tmp_path): doc.document.close() +def test_mutlinline(tmp_path): + output_path = tmp_path / "output.pdf" + commonforms.prepare_form("./tests/resources/input.pdf", output_path, fast=True, multiline=True) + + assert output_path.exists() + + doc = formalpdf.open(output_path) + assert len(doc[0].widgets()) > 0 + + doc.document.close() + + + def test_encrypted_failure(tmp_path): # Reminder to future Joe: password for encrypted PDF is "kanbanery" output_path = tmp_path / "output.pdf"