pdfplumber icon indicating copy to clipboard operation
pdfplumber copied to clipboard

PSLiteral object using non_stroking_color from a rectangle

Open luanmota opened this issue 2 years ago • 6 comments

Describe the bug

In my code I check if an obj is a rect and do some filters using the non_stroking_color property. But in one pdf the non_stroking_color is a PSLiteral obj and not a float. And if a change my code to check if the non_stroking_color is a float, the text is extracted with triple letters in each word.

Code to reproduce the problem

def get_plumber_table(page):
    tables = []
    tables = page.filter(keep_visible_lines).find_tables(
        table_settings={
            "vertical_strategy":
            "lines",
            "horizontal_strategy":
            "lines",
            "explicit_vertical_lines":
            page.filter(keep_visible_lines).curves +
            page.filter(keep_visible_lines).edges,
            "explicit_horizontal_lines":
            page.filter(keep_visible_lines).curves +
            page.filter(keep_visible_lines).edges,
        })
    return tables

def keep_visible_lines(obj):
    if obj['object_type'] == 'rect' or obj['object_type'] == 'edge':
        height = obj['height']
        width = obj['width']
        if width < 1.0 and height < 1.0:
            return False
        non_stroking_color = obj['non_stroking_color']
        if type(non_stroking_color) is tuple:
            if non_stroking_color == (0, 0, 0):
                return True
            else:
                return False
        if type(non_stroking_color) is list:
            non_stroking_color = min(non_stroking_color)
        if non_stroking_color is not None and non_stroking_color > 0.6:
            return False
    return True

with pdfplumber.open(pdf_path, laparams={}) as pdf:
        for pn in range(0, len(pdf.pages)):
            tables = get_plumber_table(page)

PDF file

Edital053_Assinado.pdf

Environment

  • pdfplumber version: latest
  • Python version: 3.8
  • OS: Linux

luanmota avatar Mar 01 '23 01:03 luanmota

Hi @luanmota Appreciate your interest in the library. The non_stroking_color in pdfplumber comes from the pdfminer.six' PDFGraphicState.ncolor. My recommendation would be to also open an issue (or start a discussion) on the pdfminer.six repo.

samkit-jain avatar Mar 01 '23 07:03 samkit-jain

Hi @luanmota, a couple of additional notes:

  • I've never heard of color components using string literals. (Interesting!) My guess is that this is against the PDF spec, although I can't find a direct source for that.

  • Try applying pdfplumber.utils. resolve_and_decode(...) on the non_stroking_color values. Does that work for you?

jsvine avatar Mar 09 '23 16:03 jsvine

Hey @jsvine thanks for your time and help!

Sorry for the "noob" question, but what exactly pdfplumber.utils.resolve_and_decode(...) does? I didn't find this function in the documentation.

Thanks!

luanmota avatar Mar 15 '23 11:03 luanmota

@luanmota, no apology necessary! That's a utility method that's mainly used internally (and thus not listed in the core documentation), but might be useful here for your edge-case. It resolves any indirect object references (not an issue for you) and converts any PSLiterals into standard text (your issue). You can see its implementation here: https://github.com/jsvine/pdfplumber/blob/ee48b26099a614b9e97465963a5ff46aa2b04e46/pdfplumber/utils/pdfinternals.py#L19-L34

jsvine avatar Mar 15 '23 15:03 jsvine

@jsvine I tested the resolve_and_decode and in some cases non_stroking_color is a list with this inside: /'Pattern1' Do you jave any ideia what it can be? I tried to find in the pdfminer.six but nothing there.

I think we can close this issue if you don't have any ideia how I can undertand what is this return. I find another problem with this PDF with duplicate chars and resolve with dedupe_chars function. Pdfplumber is a really great tool!!! Thanks again for the help :)

luanmota avatar Mar 22 '23 02:03 luanmota

Thanks for the kind words @luanmota, and thanks for the very interesting example. The PDF specification has a section ("4.6 Patterns") on patterns, and it seems like this is what the non-stroking-color value is trying to use. Per the example of p. 296–297, it seems that this approach is valid. (My mistake on thinking it was invalid earlier.)

Accessing details about the pattern is possible, using page.page_obj.resources to access the raw resource information gathered by pdfminer.six. E.g., for your example:

page = pdf.pages[33]
p1 = page.page_obj.resources["Pattern"]["Pattern1"]
print(pdfplumber.utils.resolve_and_decode(p1))

... which gives you:

{'Matrix': [0.75, 0, 0, -0.75, 0, 841.92004],
 'PatternType': 2,
 'Shading': {'ColorSpace': 'DeviceRGB',
  'Coords': [0, 152.48, 0, 153.75999],
  'Extend': [True, True],
  'Function': {'Bounds': [0.5, 0.5],
   'Domain': [0, 1],
   'Encode': [0, 1, 0, 1, 0, 1],
   'FunctionType': 3,
   'Functions': [{'C0': [0, 0, 0.03922],
     'C1': [0, 0, 0.03922],
     'Domain': [0, 1],
     'FunctionType': 2,
     'N': 1},
    {'C0': [0, 0, 0.03922],
     'C1': [0, 0, 0],
     'Domain': [0, 1],
     'FunctionType': 2,
     'N': 1},
    {'C0': [0, 0, 0],
     'C1': [0, 0, 0],
     'Domain': [0, 1],
     'FunctionType': 2,
     'N': 1}]},
  'ShadingType': 2},
 'Type': 'Pattern'}

jsvine avatar Mar 22 '23 15:03 jsvine