Skip to content

Redaction applied using Adobe Acrobat always uses fill color - white when Redaction Annotation created using pymupdf #4657

@KRMck

Description

@KRMck

Description of the bug

When creating redaction annotations with PyMuPDF, Adobe Acrobat always applies the redactions with a white fill, regardless of the fill color specified in the annotation or set using properties in app i.e. black.

How to reproduce the bug

Steps to Reproduce:
1. Use attached sample pdf as input file.

sample_index.pdf

Run below code to generate redacted preview of pdf. Generated pdf will have red outline box and black fill seen when hovering over it.

	```

import fitz

	def force_red_outline(doc, annot):
	    """
	    Replace /AP stream of a redact annotation with a red-outline box.
	    """
	    rect = annot.rect
	    x0, y0, x1, y1 = rect
	    width = x1 - x0
	    height = y1 - y0
	    # Normal appearance (red outline only)
	    normal_stream = f"""
	    q
	    1 0 0 RG    % Red stroke color
	    1 w         % 1 point line width
	    0 0 {x1 - x0} {y1 - y0} re  % Rectangle
	    S           % Stroke only
	    Q
	    """.strip().encode()
	    # Rollover/hover appearance (black fill + red outline)
	    rollover_stream = f"""
	    q
	    0 0 0 rg    % Black fill color
	    1 0 0 RG    % Red stroke color
	    1 w         % 1 point line width
	    0 0 {width} {height} re  % Rectangle
	    B           % Fill and stroke
	    Q
	    """.strip().encode()
	    # Create appearance dictionary with both normal and rollover states
	    ap_dict_normal = f"""<<
	    /Type /XObject
	    /Subtype /Form
	    /BBox [0 0 {width:.2f} {height:.2f}]
	    /Matrix [1 0 0 1 0 0]
	    /Resources << /ProcSet [/PDF] >>
	    /Length {len(normal_stream)}
	    >>"""
	    ap_dict_rollover = f"""<<
	    /Type /XObject
	    /Subtype /Form
	    /BBox [0 0 {width:.2f} {height:.2f}]
	    /Matrix [1 0 0 1 0 0]
	    /Resources << /ProcSet [/PDF] >>
	    /Length {len(rollover_stream)}
	    >>"""
	    # Add appearance streams to document
	    normal_xref = doc.get_new_xref()
	    rollover_xref = doc.get_new_xref()
	    # First create the dictionary objects
	    doc.update_object(normal_xref, ap_dict_normal)
	    doc.update_object(rollover_xref, ap_dict_rollover)
	    # Then add the stream content
	    doc.update_stream(xref=normal_xref, stream=normal_stream)
	    doc.update_stream(xref=rollover_xref, stream=rollover_stream)
	    # Set appearance dictionary with both normal and rollover states
	    ap_reference = f"<< /N {normal_xref} 0 R /R {rollover_xref} 0 R >>"
	    doc.xref_set_key(annot.xref, "AP", ap_reference)
	    doc.xref_set_key(annot.xref, "IC", "[0 0 0]")  # Interior color: BLACK
	    doc.xref_set_key(annot.xref, "C", "[1 0 0]")   # Border color: RED
	if __name__ == "__main__":
	    pdf_file = 'input.pdf'
	    sanitize_file_path = 'output.pdf'
	    doc = fitz.open(pdf_file)
	    sanitized_content = [['Blue-sky printing', 'There are many reasons for using Prince. We will show you 8.', 'one:', ' [lang]', 'three:', ' [frame] columns', 'Prince supports [lang] so that programmers can', 'achieve effects not possible in HTML and CSS along.', 'For example, this document has a small script that auto-', 'matically generates a table of contents:', 'This document is laid out in two columns on the', 'first page. The next pages are more narrow and', 'therefore only has room for one column. The num-', 'ber of columns is automatically adjusted based on', 'the available width.', '[lang]......................1', 'More [lang] ...........1', '[frame] columns...............1', 'Styling pages.................1', '[frame] transforms.......... 1', 'Background images.....2', '[frame] selectors..............2', 'More transforms.........2', '4:', ' Styling pages', 'The second page of this document is different from the first: it’s up-', 'right and it has no background image. This is achieved by styling', 'pages instead of elements.', 'two:', ' More [lang]', 'This document lists 8 numbered features. Notice that the first', 'three features are numbered with spelled-out numbers, while', 'the rest use digits. This is achieved with a small script that ex-', 'changes digits for letters when the number is three or smaller.', '5:', ' [frame] transforms', '[frame] introduces transforms, which can be used to scale and'], ['Prince', 'rotate elements. «Prince» is rotated 90 degerees on the right.', 'Also, it has been moved to a margin box so that it appears out-', 'side the normal text flow.', '6:', ' Background images', 'The image on the right is used as the', 'background image on the first page.', 'There, only part of the image is visible.', 'This is achieved by carefully setting the', 'position and pixel-density of the image.', 'Also, notice how the background image is', '«bleeding»; it extends slightly outside the', 'page to avoid white edges when the paper', 'is cut.', '7:', ' [frame] selectors', 'Notice how every other item in our list of eight is in italics. This is achieved by', 'selecting and styling even-numbered items.', '8:', ' More transforms', 'Transforms can be applied to images as well as text. Below is the blue-sky background im-', 'age rotated at various angles.']]
	    page_count=0
	    for page in doc.pages():
	        text_blocks = page.get_text("dict", flags=fitz.TEXTFLAGS_TEXT)["blocks"]
	        text_blocks.sort(
	            key=lambda span: (span["bbox"][1], span["bbox"][0])
	        )  # Sort by position
	        index = 0
	        for block in text_blocks:
	            for line in block.get("lines", []):
	                for span in line.get("spans", []):
	                    if (
	                        index >= len(sanitized_content[page_count])
	                        or span["text"] == sanitized_content[page_count][index]
	                        or span["text"] == " "
	                    ):
	                        index += 1
	                        continue  # Skip if text is already correct
	                    # Add redaction annotation that Adobe can recognize
	                    x0, y0, x1, y1 = span["bbox"]
	                    redact_rect = fitz.Rect(x0, y0, x1, y1)
	                
	                    redact_annot = page.add_redact_annot(redact_rect, text="", fill=(0, 0, 0), cross_out=False)
	                    force_red_outline(doc, redact_annot)
	                    index += 1
	        page_count += 1
	    doc.save(sanitize_file_path)
	    doc.close()
		
	2. Open this saved pdf from above step using Adobe, ensure All tools > Redact a PDF > Set properties > Redacted Fill Area color is set to black. 
	3. Apply Redaction using Apply button, all redacted boxes turns white
	4. If step 2 and 3 are repeated manually in Adobe on same pdf, you will see redacted boxes are in black.

Ask:
Is there any workaround to make fill color as black when apply redaction from Adobe till this bug is resolved?

### PyMuPDF version

1.26.3

### Operating system

MacOS

### Python version

3.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    not a bugnot a bug / user error / unable to reproduce

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions