From 62086d0540ed5e2473c82e008f8b88ac7d411c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Kreuzberger?= Date: Fri, 15 Mar 2024 10:50:31 +0100 Subject: [PATCH 1/2] Color and font information for chars, words and boxes (#39) The information originates in chars, but is pushed to words, lines and boxes in case the values don't differ. --- libpdf/models/horizontal_box.py | 45 +++++++++ libpdf/utils.py | 10 +- tests/conftest.py | 3 + tests/pdf/test_words_color_style.odt | Bin 0 -> 26892 bytes tests/pdf/test_words_color_style.pdf | Bin 0 -> 35089 bytes tests/test_word_colors.py | 143 +++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 tests/pdf/test_words_color_style.odt create mode 100644 tests/pdf/test_words_color_style.pdf create mode 100644 tests/test_word_colors.py diff --git a/libpdf/models/horizontal_box.py b/libpdf/models/horizontal_box.py index 6dda06c..cf8015d 100644 --- a/libpdf/models/horizontal_box.py +++ b/libpdf/models/horizontal_box.py @@ -19,6 +19,8 @@ class Char: # pylint: disable=too-few-public-methods # simplicity is good. :ivar y1: distance from the bottom of the page to the upper edge of the character (greater than y0) :vartype y1: float + :ivar ncolor: non-stroking-color as rgb value + :vartype ncolor: Tuple[float, float, float] """ def __init__( @@ -28,6 +30,8 @@ def __init__( y0: float | None = None, x1: float | None = None, y1: float | None = None, + ncolor: tuple | None = None, + fontname: str | None = None, ): """Init with plain char of a character and its rectangular coordinates.""" self.x0 = x0 @@ -35,6 +39,8 @@ def __init__( self.x1 = x1 self.y1 = y1 self.text = text + self.ncolor = ncolor + self.fontname = fontname def __repr__(self) -> str: """Make the text part of the repr for better debugging.""" @@ -65,6 +71,9 @@ def __init__( self.x1 = x1 self.y1 = y1 self.chars = chars + self.ncolor = None + self.fontname = None + if self.chars: # Obtain the rectangle coordinates from a list of libpdf text objects self.x0 = min(text_obj.x0 for text_obj in self.chars) @@ -72,6 +81,14 @@ def __init__( self.x1 = max(text_obj.x1 for text_obj in self.chars) self.y1 = max(text_obj.y1 for text_obj in self.chars) + for n in ["ncolor", "fontname"]: + if all( + getattr(x, n) == getattr(self.chars[0], n) + and getattr(x, n) is not None + for x in self.chars + ): + setattr(self, n, getattr(self.chars[0], n)) + @property def text(self) -> str: """Return plain text.""" @@ -106,6 +123,9 @@ def __init__( self.x1 = x1 self.y1 = y1 self.words = words + self.ncolor = None + self.fontname = None + if self.words: # Obtain the rectangle coordinates from a list of libpdf text objects self.x0 = min(text_obj.x0 for text_obj in self.words) @@ -113,6 +133,14 @@ def __init__( self.x1 = max(text_obj.x1 for text_obj in self.words) self.y1 = max(text_obj.y1 for text_obj in self.words) + for n in ["ncolor", "fontname"]: + if all( + getattr(x, n) == getattr(self.words[0], n) + and getattr(x, n) is not None + for x in self.words + ): + setattr(self, n, getattr(self.words[0], n)) + @property def text(self) -> str: """Return plain text.""" @@ -147,6 +175,9 @@ def __init__( self.x1 = x1 self.y1 = y1 self.lines = lines + self.ncolor = None + self.fontname = None + if self.lines: # Obtain the rectangle coordinates from a list of libpdf text objects. self.x0 = min(text_obj.x0 for text_obj in self.lines) @@ -154,11 +185,25 @@ def __init__( self.x1 = max(text_obj.x1 for text_obj in self.lines) self.y1 = max(text_obj.y1 for text_obj in self.lines) + _words = [word for line in self.lines for word in line.words] + + for n in ["ncolor", "fontname"]: + if all( + getattr(x, n) == getattr(_words[0], n) and getattr(x, n) is not None + for x in _words + ): + setattr(self, n, getattr(_words[0], n)) + @property def text(self) -> str: """Return plain text.""" return "\n".join([x.text for x in self.lines]) + @property + def words(self) -> list[str]: + """Return list of words.""" + return [word for line in self.lines for word in line.words] + def __repr__(self) -> str | None: """Make the text part of the repr for better debugging.""" if self.lines: diff --git a/libpdf/utils.py b/libpdf/utils.py index 963af11..f01d219 100644 --- a/libpdf/utils.py +++ b/libpdf/utils.py @@ -488,7 +488,15 @@ def assemble_to_textlines( for lt_obj in flatten_lt_objs: if lt_obj.get_text() != " " and lt_obj.get_text() != "\n": # instantiate Char - char = Char(lt_obj.get_text(), lt_obj.x0, lt_obj.y0, lt_obj.x1, lt_obj.y1) + char = Char( + lt_obj.get_text(), + lt_obj.x0, + lt_obj.y0, + lt_obj.x1, + lt_obj.y1, + lt_obj.graphicstate.ncolor if hasattr(lt_obj, "graphicstate") else None, + lt_obj.fontname, + ) chars.append(char) if lt_obj is flatten_lt_objs[-1]: diff --git a/tests/conftest.py b/tests/conftest.py index e5b7bb0..f0a1b39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,6 +34,9 @@ # test PDF for rect extraction generateby by sphinx-simplepdf PDF_RECTS_EXTRACTION = Path(__file__).parent / "pdf" / "test_rects_extraction.pdf" +# test PDF for color style info +PDF_COLOR_STYLE = Path(__file__).parent / "pdf" / "test_words_color_style.pdf" + @pytest.fixture(scope="session") def load_full_features_pdf( diff --git a/tests/pdf/test_words_color_style.odt b/tests/pdf/test_words_color_style.odt new file mode 100644 index 0000000000000000000000000000000000000000..98fa5a0728890235ecea3b9539576a795d3a7521 GIT binary patch literal 26892 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�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%0A;UgvIC=gw!E(Q+g1fV;$0+?#KBgf>UpFz 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 r%*@Uz%*-spE-WkvS None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if chapter.title == "Color in Text and Heading": + assert chapter.textbox.ncolor == (1, 0, 0) + + +def test_colors_1() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if chapter.title == "HorizontalLine": + for content in chapter.content: + if ( + content.type == "paragraph" + and "Paragraph text is blue" in content.textbox.text + ): + assert content.textbox.ncolor == (0, 0, 1) + if ( + content.type == "paragraph" + and "This chapter is for" in content.textbox.text + ): + assert content.textbox.ncolor == (0, 0, 0) + + +def test_colors_2() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if chapter.title == "HorizontalBox": + for content in chapter.content: + if content.type == "paragraph": + assert content.textbox.ncolor == (0, 1, 0) + elif chapter.title == "UncoloredHorizontalbox": + for content in chapter.content: + if content.type == "paragraph": + assert content.textbox.ncolor is None + for line in content.textbox.lines: + assert line.ncolor is not None + + +def test_colors_3() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if "Words" in chapter.title: + for content in chapter.content: + if ( + content.type == "paragraph" + and "This line has no color" in content.textbox.text + ): + assert content.textbox.ncolor is None + + for word in content.textbox.words: + if word.text == "has": + assert word.ncolor == (0, 0, 1) + elif word.text == "color": + assert word.ncolor in [(0, 1, 0), (0, 0, 0)] + elif word.text == "changes": + assert word.ncolor == (1, 0, 0) + elif word.text == "words": + assert word.ncolor == (0, 0, 1) + + +def test_colors_4() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if "Words" in chapter.title: + for content in chapter.content: + if "This words have no color" in content.textbox.text: + assert content.textbox.ncolor is None + + for word in content.textbox.words: + assert word.ncolor is None or word.ncolor == (0, 0, 0) + + +def test_colors_5() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if "Words" in chapter.title: + for content in chapter.content: + if "These words are printed" in content.textbox.text: + assert content.textbox.ncolor is None + + for word in content.textbox.words: + if word.text in ["words", "but"]: + assert word.ncolor == (0, 1, 0) + elif word.text == "printed": + assert word.ncolor == (0, 0, 1) + elif word.text == "background": + assert word.ncolor == (1, 0, 0) + + +def test_colors_6() -> None: + """Test word colors in given chapter paragraph.""" + objects = libpdf.load(PDF_COLOR_STYLE) + assert objects is not None + assert objects.flattened.chapters + + for chapter in objects.flattened.chapters: + if "Styled Text" in chapter.title: + for content in chapter.content: + if "bold text format" in content.textbox.text: + for word in content.textbox.words: + if word.text == "bold": + assert "Bold" in word.fontname + else: + assert "Bold" not in word.fontname + elif "italic text format" in content.textbox.text: + if word.text == "italic": + assert "Italic" in word.fontname + else: + assert "Italic" not in word.fontname + elif "underline text format" in content.textbox.text: + # this seems to be exracted as rect + pass From f55bafe5d79d9c3908e1193deb1469496236e82a Mon Sep 17 00:00:00 2001 From: Joerg Kreuzberger Date: Wed, 18 Sep 2024 12:00:55 +0200 Subject: [PATCH 2/2] fix handling with invalid bboxes --- libpdf/catalog.py | 8 +++++ tests/test_figures.py | 74 ++++++++++++++++++------------------------- tests/test_rects.py | 3 +- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/libpdf/catalog.py b/libpdf/catalog.py index f5f639b..8ea605e 100644 --- a/libpdf/catalog.py +++ b/libpdf/catalog.py @@ -436,6 +436,14 @@ def update_ann_info(annotation_page_map, ann_resolved, page, idx_page, pdf) -> N float(ann_resolved["Rect"][3]) + ANNO_Y_TOLERANCE, page.height, ) + + left, top, right, bottom = ann_bbox + if top > bottom: + LOG.debug(f"invalid annotation bbox: {ann_resolved['Rect']}, {ann_bbox}") + return + # maybe continue with swapped bbox + # ann_bbox = [left, bottom, right, top] + page_crop = page.within_bbox(ann_bbox) ann_text = page_crop.extract_text(x_tolerance=1, y_tolerance=4) diff --git a/tests/test_figures.py b/tests/test_figures.py index 1b4e4d0..9a1dd62 100644 --- a/tests/test_figures.py +++ b/tests/test_figures.py @@ -19,19 +19,7 @@ def test_figures_extract_with_invalid_bbox(): objects = libpdf.load(PDF_FIGURE_WITH_INVALID_BBOX) assert objects is not None # extract figures only with valid bbox - assert len(objects.pdfplumber.pages[0].figures) == 1 - assert objects.pdfplumber.pages[0].figures[0]["height"] == 0 - assert ( - objects.pdfplumber.pages[0].figures[0]["y0"] - == objects.pdfplumber.pages[0].figures[0]["y1"] - ) - - assert len(objects.pdfplumber.pages[1].figures) == 1 - assert objects.pdfplumber.pages[1].figures[0]["height"] == 0 - assert ( - objects.pdfplumber.pages[1].figures[0]["y0"] - == objects.pdfplumber.pages[1].figures[0]["y1"] - ) + assert len(objects.pdfplumber.pages[0].images) == 0 assert not objects.flattened.figures @@ -41,68 +29,68 @@ def test_figures_extraction(): objects = libpdf.load(PDF_FIGURES_EXTRACTION) assert objects.flattened.figures is not None - assert len(objects.pdfplumber.figures) == 6 + assert len(objects.pdfplumber.images) == 6 assert len(objects.flattened.figures) == 2 # filter figure with negative position, partially outside page - assert objects.pdfplumber.figures[2]["x0"] < 0 + assert objects.pdfplumber.images[2]["x0"] < 0 # check that figure exists no more assert objects.flattened.figures[0].position.x0 >= 0 assert objects.flattened.figures[1].position.x0 >= 0 # filter figures that are too small - assert objects.pdfplumber.figures[4]["width"] < 15 - assert objects.pdfplumber.figures[4]["height"] < 15 + assert objects.pdfplumber.images[4]["width"] < 15 + assert objects.pdfplumber.images[4]["height"] < 15 # check that figure exists no more for figure in objects.flattened.figures: assert figure.position.x1 - figure.position.x0 >= 15 assert figure.position.y1 - figure.position.y0 >= 15 # filter figures that are completely inside other figures - assert objects.pdfplumber.figures[1]["x0"] > objects.pdfplumber.figures[0]["x0"] - assert objects.pdfplumber.figures[1]["y0"] > objects.pdfplumber.figures[0]["y0"] - assert objects.pdfplumber.figures[1]["x1"] < objects.pdfplumber.figures[0]["x1"] - assert objects.pdfplumber.figures[1]["y1"] < objects.pdfplumber.figures[0]["y1"] + assert objects.pdfplumber.images[1]["x0"] > objects.pdfplumber.images[0]["x0"] + assert objects.pdfplumber.images[1]["y0"] > objects.pdfplumber.images[0]["y0"] + assert objects.pdfplumber.images[1]["x1"] < objects.pdfplumber.images[0]["x1"] + assert objects.pdfplumber.images[1]["y1"] < objects.pdfplumber.images[0]["y1"] # check that figure exists no more for figure in objects.flattened.figures: - assert abs(float(objects.pdfplumber.figures[1]["x0"]) - figure.position.x0) > 1 - assert abs(float(objects.pdfplumber.figures[1]["y0"]) - figure.position.y0) > 1 - assert abs(float(objects.pdfplumber.figures[1]["x1"]) - figure.position.x1) > 1 - assert abs(float(objects.pdfplumber.figures[1]["y1"]) - figure.position.y1) > 1 + assert abs(float(objects.pdfplumber.images[1]["x0"]) - figure.position.x0) > 1 + assert abs(float(objects.pdfplumber.images[1]["y0"]) - figure.position.y0) > 1 + assert abs(float(objects.pdfplumber.images[1]["x1"]) - figure.position.x1) > 1 + assert abs(float(objects.pdfplumber.images[1]["y1"]) - figure.position.y1) > 1 # filter figures that are partially overlap with other figure, remove the smaller figure - assert objects.pdfplumber.figures[3]["x0"] < objects.pdfplumber.figures[5]["x0"] - assert objects.pdfplumber.figures[3]["y0"] < objects.pdfplumber.figures[5]["y0"] - assert objects.pdfplumber.figures[3]["x1"] < objects.pdfplumber.figures[5]["x1"] - assert objects.pdfplumber.figures[3]["y1"] < objects.pdfplumber.figures[5]["y1"] + assert objects.pdfplumber.images[3]["x0"] < objects.pdfplumber.images[5]["x0"] + assert objects.pdfplumber.images[3]["y0"] < objects.pdfplumber.images[5]["y0"] + assert objects.pdfplumber.images[3]["x1"] < objects.pdfplumber.images[5]["x1"] + assert objects.pdfplumber.images[3]["y1"] < objects.pdfplumber.images[5]["y1"] assert ( - objects.pdfplumber.figures[3]["width"] * objects.pdfplumber.figures[3]["height"] - < objects.pdfplumber.figures[5]["width"] - * objects.pdfplumber.figures[5]["height"] + objects.pdfplumber.images[3]["width"] * objects.pdfplumber.images[3]["height"] + < objects.pdfplumber.images[5]["width"] + * objects.pdfplumber.images[5]["height"] ) # check that figure exists no more for figure in objects.flattened.figures: - assert abs(float(objects.pdfplumber.figures[3]["x0"]) - figure.position.x0) > 1 - assert abs(float(objects.pdfplumber.figures[3]["y0"]) - figure.position.y0) > 1 - assert abs(float(objects.pdfplumber.figures[3]["x1"]) - figure.position.x1) > 1 - assert abs(float(objects.pdfplumber.figures[3]["y1"]) - figure.position.y1) > 1 + assert abs(float(objects.pdfplumber.images[3]["x0"]) - figure.position.x0) > 1 + assert abs(float(objects.pdfplumber.images[3]["y0"]) - figure.position.y0) > 1 + assert abs(float(objects.pdfplumber.images[3]["x1"]) - figure.position.x1) > 1 + assert abs(float(objects.pdfplumber.images[3]["y1"]) - figure.position.y1) > 1 def test_remove_figures_in_header_footer(): """Remove figures that in header and footer.""" objects = libpdf.load(PDF_FULL_FEATURES, smart_page_crop=True) - assert len(objects.pdfplumber.figures) == 7 + assert len(objects.pdfplumber.images) == 7 assert len(objects.flattened.figures) == 2 # on page 1, there are two figures, one is in header - assert objects.pdfplumber.figures[0]["page_number"] == 1 + assert objects.pdfplumber.images[0]["page_number"] == 1 # figures[0] on page 1 is not in header - assert float(objects.pdfplumber.figures[0]["y0"]) == 239.15 - assert float(objects.pdfplumber.figures[0]["y1"]) == 382.85 + assert float(objects.pdfplumber.images[0]["y0"]) == 239.15 + assert float(objects.pdfplumber.images[0]["y1"]) == 382.85 # figures[1] on page 1 is in header - assert objects.pdfplumber.figures[1]["page_number"] == 1 - assert float(objects.pdfplumber.figures[1]["y0"]) == 719.4 - assert float(objects.pdfplumber.figures[1]["y1"]) == 754.05 + assert objects.pdfplumber.images[1]["page_number"] == 1 + assert float(objects.pdfplumber.images[1]["y0"]) == 719.4 + assert float(objects.pdfplumber.images[1]["y1"]) == 754.05 # libpdf extract_figures removed that figure in header, only one figure left on page 1 assert objects.flattened.figures[0].position.page.number == 1 diff --git a/tests/test_rects.py b/tests/test_rects.py index 6393151..617ebf0 100644 --- a/tests/test_rects.py +++ b/tests/test_rects.py @@ -230,4 +230,5 @@ def test_rects_extraction_table() -> None: assert table.columns_count == 1 * 3 assert table.rows_count == 1 - assert check_chapter_rects_count(chapter) == 1 * 5 + # assert check_chapter_rects_count(chapter) == 1 * 5 + assert check_chapter_rects_count(chapter) == 17