pypdf icon indicating copy to clipboard operation
pypdf copied to clipboard

ENH: Support transparency channel (RGBA) in annotations interiour_color

Open vors opened this issue 2 years ago • 11 comments

Explanation

I'd like to do highlighting of a certain parts of the pdf, but I want it to be transept so the original pdf is readable. The proposal is to support RGBA in annotation to allow for it

Code Example

How would your feature be used? (Remove this if it is not applicable.)

  annotation = annotation_builder.rectangle(
      rect=(x[0], y[0], x[0] + 10, y[0] + 30),
      # transparency part of the RGBA is currently ignored, can we support it?
      interiour_color="f1e740aa",
  )

vors avatar Sep 18 '23 15:09 vors

quad components in PDF means most of the time CMYK components. Transparency should be considered as an independent attribute

pubpub-zz avatar Sep 18 '23 21:09 pubpub-zz

sounds good to me, so something like?

annotation = annotation_builder.rectangle(
      rect=(x[0], y[0], x[0] + 10, y[0] + 30),
      interiour_color="f1e740",
      transparency=0.5,
  )

vors avatar Sep 18 '23 21:09 vors

Here is a function I created to set opacity for annotation

def annotation_set_opacity(annotation: AnnotationDictionary, opacity: float):
    pdf_writer: PdfWriter = annotation.indirect_reference.pdf
    rect = cast(RectangleObject, annotation["/Rect"])
    x = rect[0]
    y = rect[1]
    width = rect.width
    height = rect.height
    line_width = 1
    color = cast(ArrayObject, annotation["/IC"])

    # https://pikepdf.readthedocs.io/en/latest/topics/content_streams.html
    # https://ghostscript.com/~robin/pdf_reference17.pdf
    ap_stream = (
        "q\n"  # Save the current graphics state on the graphics state stack (see “Graphics State Stack” on page 214).
        "/H gs\n"  # Set the specified parameters in the graphics state. dictName is the name of a
        # graphics state parameter dictionary in the ExtGState subdictionary of the current resource dictionary
        f"{line_width} w\n"  # Set the line width in the graphics state (see “Line Width” on page 215).
        f"{color[0]} {color[1]} {color[2]} rg\n"  # Set nonstroking colour
        f"{x} {y} {width} {height} re\n"  # Construct rectangular path
        # x y width height re
        # Append a rectangle to the current path as a complete subpath, with
        # lower-left corner (x, y) and dimensions width and height in user
        # space.
        # page 227
        "f\n"  # Fill path
        "\nQ"  # Pop graphics stack.
    ).encode()

    try:
        x_object = cast(DictionaryObject, annotation["/AP"])["/N"]
        x_object = cast(DecodedStreamObject, x_object)
        x_object.set_data(ap_stream)
    except KeyError:
        x_object = DecodedStreamObject.initialize_from_dictionary(
            {
                NameObject("/Type"): NameObject("/XObject"),
                NameObject("/Subtype"): NameObject("/Form"),
                NameObject("/BBox"): rect,
                NameObject("/Matrix"): ArrayObject(
                    [
                        NumberObject(1),
                        NumberObject(0),
                        NumberObject(0),
                        NumberObject(1),
                        NumberObject(0),
                        NumberObject(0),
                    ]
                ),
                NameObject("/Resources"): DictionaryObject(),
                "__streamdata__": ByteStringObject(ap_stream),
                "/Length": 0,
            }
        )
    resources = cast(DictionaryObject, x_object.setdefault(NameObject("/Resources"), DictionaryObject()))

    extg = cast(DictionaryObject, resources.setdefault(NameObject("/ExtGState"), DictionaryObject()))
    ext_g_state_h = cast(DictionaryObject, extg.setdefault(NameObject("/H"), DictionaryObject()))
    ext_g_state_h[NameObject("/ca")] = FloatObject(opacity)
    ext_g_state_h[NameObject("/CA")] = FloatObject(opacity)

    # noinspection PyProtectedMember
    pdf_writer._add_object(x_object)
    annotation_ap = cast(DictionaryObject, annotation.setdefault(NameObject("/AP"), DictionaryObject()))
    annotation_ap[NameObject("/N")] = x_object.indirect_reference
    annotation[NameObject("/CA")] = FloatObject(opacity)

must be called after writer.add_annotation for example:

writer.add_annotation(page_number=0, annotation=annotation)
annotation_set_opacity(annotation, 0.3)

sky-code avatar Nov 04 '23 22:11 sky-code

amazing, I will try it out next time I need it!

vors avatar Nov 04 '23 22:11 vors

@sky-code you should push it into Discussion to ease people to find the information.

pubpub-zz avatar Nov 05 '23 19:11 pubpub-zz

Omg I needed this too thank you so much @vors is it possible to add this feature to main?

avishaan avatar Nov 05 '23 21:11 avishaan

Looks awesome! I definitely need that feature

ofilimonov avatar Nov 06 '23 16:11 ofilimonov

:shipit: 🙏 Yes please!

msisto avatar Nov 06 '23 16:11 msisto

Here is a function I created to set opacity for annotation

def annotation_set_opacity(annotation: AnnotationDictionary, opacity: float):
    ...

must be called after writer.add_annotation for example:

writer.add_annotation(page_number=0, annotation=annotation)
annotation_set_opacity(annotation, 0.3)

Great work, but could it be that this only works for annotations on the first page? Or does I missed something? If I add the annotation to the second page and set the opacity with your function, the result shows a full transparent rectangle on the second page and the colored background with opacity on the first page.

andy71 avatar Nov 07 '23 12:11 andy71

Ohh yeah, I need this feature as well 🙏

aglov avatar Nov 10 '23 20:11 aglov