Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.25.2

### Enhancements

- **Adopt `core-pdfminer.six` for PDF extraction**: migrate the PDF extraction path to the performance-oriented `core-pdfminer.six` fork while preserving upstream parsing and extraction semantics. The original `pdfminer.six` image-extra dependency remains available for compatibility.

## 0.25.1

### Fixes
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ epub = [
"pypandoc-binary>=1.16.2, <2.0.0; platform_system == 'Windows' and python_version < '3.13'",
]
image = [
"core-pdfminer-six>=0.0.0",
"google-cloud-vision>=3.12.1, <4.0.0",
"pdf2image>=1.17.0, <2.0.0",
"pdfminer.six>=20251230, <20270000",
Expand Down
2 changes: 1 addition & 1 deletion scripts/image/test-outbound-connectivity.sh
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ logging.getLogger("urllib").setLevel(logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)
logging.getLogger("httpx").setLevel(logging.DEBUG)
logging.getLogger("httpcore").setLevel(logging.DEBUG)
logging.getLogger("pdfminer.pdfpage").setLevel(logging.CRITICAL)
logging.getLogger("core_pdfminer_six.pdfpage").setLevel(logging.CRITICAL)

for test_file in [
"/app/example-docs/ideas-page.html",
Expand Down
2 changes: 1 addition & 1 deletion test_unstructured/file_utils/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def and_it_knows_which_pip_extra_needs_to_be_installed_to_get_those_dependencies
(FileType.EMPTY, ()),
(FileType.HTML, ()),
(FileType.ODT, ("docx", "pypandoc")),
(FileType.PDF, ("pdf2image", "pdfminer", "PIL")),
(FileType.PDF, ("pdf2image", "core_pdfminer_six", "PIL")),
(FileType.UNK, ()),
(FileType.WAV, ()), # STT agent deps validated at runtime
(FileType.ZIP, ()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import numpy as np
import pytest
from pdfminer.layout import LAParams, LTChar, LTContainer
from core_pdfminer_six.layout import LAParams, LTChar, LTContainer
from PIL import Image
from unstructured_inference.constants import IsExtracted
from unstructured_inference.constants import Source as InferenceSource
Expand Down Expand Up @@ -418,7 +418,7 @@ def _build_synthetic_form_pdf(path: str) -> None:

def test_get_widget_text_from_annots_extracts_filled_text_fields():
"""The widget helper recovers filled /Tx field values and skips empty ones."""
from pdfminer.pdfpage import PDFPage
from core_pdfminer_six.pdfpage import PDFPage

with tempfile.TemporaryDirectory() as tmp_dir:
pdf_path = os.path.join(tmp_dir, "form.pdf")
Expand All @@ -434,7 +434,7 @@ def test_get_widget_text_from_annots_extracts_filled_text_fields():


def test_get_widget_text_from_annots_decodes_utf16_text_without_bom():
from pdfminer.psparser import PSLiteral
from core_pdfminer_six.psparser import PSLiteral

widgets = get_widget_text_from_annots(
[
Expand All @@ -452,7 +452,7 @@ def test_get_widget_text_from_annots_decodes_utf16_text_without_bom():


def test_get_widget_text_from_annots_decodes_choice_field_value_arrays():
from pdfminer.psparser import PSLiteral
from core_pdfminer_six.psparser import PSLiteral

widgets = get_widget_text_from_annots(
[
Expand All @@ -475,7 +475,7 @@ def test_get_widget_text_from_annots_inherits_field_type_and_value_from_parent()
The intermediate parent is a direct dict (the case PDFObjRef-only traversal missed) and
the root parent carries the inherited /FT, exercising multi-level inheritance.
"""
from pdfminer.psparser import PSLiteral
from core_pdfminer_six.psparser import PSLiteral

root_parent = {"FT": PSLiteral("Tx")} # field type lives at the top of the hierarchy
mid_parent = {"V": b"\xfe\xff\x00J\x00a\x00n\x00e", "Parent": root_parent} # value mid-chain
Expand Down
16 changes: 8 additions & 8 deletions test_unstructured/partition/pdf_image/test_pdfminer_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from importlib import reload
from unittest.mock import MagicMock, patch

from pdfminer.layout import LTChar, LTContainer, LTFigure, LTLayoutContainer, LTTextLine
from pdfminer.pdftypes import PDFStream
from core_pdfminer_six.layout import LTChar, LTContainer, LTFigure, LTLayoutContainer, LTTextLine
from core_pdfminer_six.pdftypes import PDFStream

from test_unstructured.unit_utils import example_doc_path
from unstructured.partition.pdf import partition_pdf
Expand Down Expand Up @@ -525,7 +525,7 @@ def test_oversized_flate_stream_rejected_before_materialization(self):
without fully materializing the output, and the stream should not be mutated."""
import zlib

from pdfminer.psparser import LIT
from core_pdfminer_six.psparser import LIT

from unstructured.partition.pdf_image.pdfminer_utils import (
_decode_pdfstream_with_limit,
Expand All @@ -544,7 +544,7 @@ def test_normal_stream_decodes_within_limit(self):
"""A stream that fits within the limit should decode successfully."""
import zlib

from pdfminer.psparser import LIT
from core_pdfminer_six.psparser import LIT

from unstructured.partition.pdf_image.pdfminer_utils import (
_decode_pdfstream_with_limit,
Expand Down Expand Up @@ -593,8 +593,8 @@ def test_vertical_wmode_sets_font_vertical_flag(self):
font.default_disp != 0 at construction time, not just on the CMap."""
import zlib

from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.psparser import LIT
from core_pdfminer_six.pdfinterp import PDFResourceManager
from core_pdfminer_six.psparser import LIT

from unstructured.partition.pdf_image.pdfminer_utils import CustomPDFCIDFont

Expand Down Expand Up @@ -684,7 +684,7 @@ class TestCustomPDFResourceManager:

def test_returns_custom_cidfont_for_cid_subtypes(self):
"""CIDFontType2 fonts should be constructed as CustomPDFCIDFont."""
from pdfminer.psparser import LIT
from core_pdfminer_six.psparser import LIT

from unstructured.partition.pdf_image.pdfminer_utils import (
CustomPDFCIDFont,
Expand Down Expand Up @@ -738,7 +738,7 @@ def test_returns_custom_cidfont_for_cid_subtypes(self):

def test_caches_font_on_repeated_calls(self):
"""Repeated get_font calls with the same objid should return the cached instance."""
from pdfminer.psparser import LIT
from core_pdfminer_six.psparser import LIT

from unstructured.partition.pdf_image.pdfminer_utils import (
CustomPDFResourceManager,
Expand Down
2 changes: 1 addition & 1 deletion unstructured/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.25.1" # pragma: no cover
__version__ = "0.25.2" # pragma: no cover
2 changes: 1 addition & 1 deletion unstructured/file_utils/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ def partitioner_shortname(self) -> str | None:
PDF = (
"pdf",
"pdf",
["pdf2image", "pdfminer", "PIL"],
["pdf2image", "core_pdfminer_six", "PIL"],
"pdf",
[".pdf"],
"application/pdf",
Expand Down
18 changes: 7 additions & 11 deletions unstructured/partition/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

import numpy as np
import wrapt
from pdfminer.layout import LTContainer, LTImage, LTItem, LTTextBox
from pdfminer.utils import open_filename
from core_pdfminer_six.layout import LTContainer, LTImage, LTItem, LTTextBox
from core_pdfminer_six.utils import open_filename
from pi_heif import register_heif_opener
from PIL import Image as PILImage
from pypdf import PdfReader
Expand Down Expand Up @@ -77,20 +77,13 @@
PartitionStrategy,
)
from unstructured.partition.utils.sorting import coord_has_valid_points, sort_page_elements
from unstructured.patches.pdfminer import patch_psparser
from unstructured.utils import first, requires_dependencies

if TYPE_CHECKING:
from unstructured_inference.inference.layout import DocumentLayout
from unstructured_inference.inference.layoutelement import LayoutElement


# Correct a bug that was introduced by a previous patch to
# pdfminer.six, causing needless and unsuccessful repairing of PDFs
# which were not actually broken.
patch_psparser()


RE_MULTISPACE_INCLUDING_NEWLINES = re.compile(pattern=r"\s+", flags=re.DOTALL)
# Regex patterns for counting graphics and text operators in PDF content streams.
GRAPHICS_OPS_PATTERN = re.compile(
Expand Down Expand Up @@ -476,7 +469,7 @@ def _partition_pdf_with_pdfminer(
return elements


@requires_dependencies("pdfminer")
@requires_dependencies("core_pdfminer_six")
def _process_pdfminer_pages(
fp: IO[bytes],
filename: str,
Expand Down Expand Up @@ -1268,7 +1261,10 @@ def _extract_text(item: LTItem) -> str:
# They throw an error when we call interpreter.process_page
# Since we don't need color info, we can just drop it in the pdfminer code
# See #2059
@wrapt.patch_function_wrapper("pdfminer.pdfinterp", "PDFPageInterpreter.init_resources")
@wrapt.patch_function_wrapper(
"core_pdfminer_six.pdfinterp",
"PDFPageInterpreter.init_resources",
)
def pdfminer_interpreter_init_resources(wrapped, instance, args, kwargs):
resources = args[0]
if "ColorSpace" in resources:
Expand Down
6 changes: 3 additions & 3 deletions unstructured/partition/pdf_image/pdfminer_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from typing import TYPE_CHECKING, Any, BinaryIO, Iterable, List, Optional, Union, cast

import numpy as np
from pdfminer.layout import LAParams, LTChar, LTContainer, LTTextBox
from pdfminer.pdftypes import PDFObjRef
from pdfminer.utils import decode_text, open_filename
from core_pdfminer_six.layout import LAParams, LTChar, LTContainer, LTTextBox
from core_pdfminer_six.pdftypes import PDFObjRef
from core_pdfminer_six.utils import decode_text, open_filename
from unstructured_inference.config import inference_config
from unstructured_inference.constants import FULL_PAGE_REGION_THRESHOLD, IsExtracted
from unstructured_inference.inference.elements import Rectangle
Expand Down
24 changes: 12 additions & 12 deletions unstructured/partition/pdf_image/pdfminer_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
import zlib
from typing import BinaryIO, List, Mapping, Optional, Tuple, Union

from pdfminer import settings as pdfminer_settings
from pdfminer.cmapdb import CMap, CMapDB
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LAParams, LTChar, LTContainer, LTImage, LTItem, LTTextLine
from pdfminer.pdffont import PDFCIDFont, PDFFontError
from pdfminer.pdfinterp import LITERAL_FONT, PDFPageInterpreter, PDFResourceManager
from pdfminer.pdfpage import PDFPage
from pdfminer.pdftypes import (
from core_pdfminer_six import settings as pdfminer_settings
from core_pdfminer_six.cmapdb import CMap, CMapDB
from core_pdfminer_six.converter import PDFPageAggregator
from core_pdfminer_six.layout import LAParams, LTChar, LTContainer, LTImage, LTItem, LTTextLine
from core_pdfminer_six.pdffont import PDFCIDFont, PDFFontError
from core_pdfminer_six.pdfinterp import LITERAL_FONT, PDFPageInterpreter, PDFResourceManager
from core_pdfminer_six.pdfpage import PDFPage
from core_pdfminer_six.pdftypes import (
LITERALS_ASCII85_DECODE,
LITERALS_ASCIIHEX_DECODE,
LITERALS_FLATE_DECODE,
PDFStream,
resolve1,
)
from pdfminer.psexceptions import PSSyntaxError
from pdfminer.psparser import literal_name
from core_pdfminer_six.psexceptions import PSSyntaxError
from core_pdfminer_six.psparser import literal_name
from pydantic import BaseModel

from unstructured.logger import logger
Expand Down Expand Up @@ -185,11 +185,11 @@ def _decode_pdfstream_with_limit(stream: PDFStream, max_decoded_bytes: int) -> O
return None
data = result
elif filt in LITERALS_ASCII85_DECODE:
from pdfminer.ascii85 import ascii85decode
from core_pdfminer_six.ascii85 import ascii85decode

data = ascii85decode(data)
elif filt in LITERALS_ASCIIHEX_DECODE:
from pdfminer.ascii85 import asciihexdecode
from core_pdfminer_six.ascii85 import asciihexdecode

data = asciihexdecode(data)
else:
Expand Down
76 changes: 0 additions & 76 deletions unstructured/patches/pdfminer.py

This file was deleted.

Loading
Loading