| """Shared machinery for every redaction pass. |
| |
| A *finding* is a dict describing one glyph region to remove and what to put |
| back: |
| |
| { |
| "page": int, # 0-based page index |
| "rect": fitz.Rect, # glyphs to TRULY remove (apply_redactions) |
| "box": fitz.Rect, # region to fit the replacement text into |
| "text": str, # replacement (width-preserving filler / token) |
| } |
| |
| Every pass produces findings; `apply_and_fill` removes then re-inserts them in |
| one shot, so the fixed-width column structure of the report is preserved and |
| the original glyphs are genuinely gone (not merely covered). |
| """ |
| import fitz |
|
|
|
|
| def has_text_layer(doc): |
| """True if the PDF has any extractable text (i.e. not a pure scan).""" |
| return sum(len(p.get_text("text")) for p in doc) > 0 |
|
|
|
|
| def page_texts(doc): |
| return [p.get_text("text") for p in doc] |
|
|
|
|
| def _insert_fit(page, box, text): |
| """Insert monospace `text` sized to fill `box` (Courier => same pitch as |
| the surrounding fixed-width report text).""" |
| n = max(len(text), 1) |
| fs = min((box.width / n) / 0.6, box.height) |
| page.insert_text((box.x0, box.y1 - box.height * 0.18), text, |
| fontname="cour", fontsize=fs, color=(0, 0, 0)) |
|
|
|
|
| def apply_and_fill(doc, findings): |
| """Remove every finding's glyphs, then re-insert its replacement text.""" |
| for f in findings: |
| doc[f["page"]].add_redact_annot(f["rect"], fill=(1, 1, 1)) |
| for page in doc: |
| page.apply_redactions(images=fitz.PDF_REDACT_IMAGE_NONE) |
| for f in findings: |
| _insert_fit(doc[f["page"]], f.get("box") or f["rect"], f["text"]) |
|
|
|
|
| def scrub_metadata(doc): |
| doc.set_metadata({}) |
| try: |
| doc.del_xml_metadata() |
| except Exception: |
| pass |
|
|