Skip to content

Commit 6c50c51

Browse files
IgorTavcarclaude
andcommitted
Support bytes and file objects in load_pdf
Allows load_pdf to accept raw bytes or binary file-like objects in addition to file paths, useful for processing cloud-stored PDFs. Based on: Layout-Parser#153 (davibarreira) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 66fc3ea commit 6c50c51

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

src/layoutparser/io/pdf.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import List, Union, Optional, Dict, Tuple
15+
from io import BytesIO
16+
from typing import BinaryIO, List, Union, Optional, Dict, Tuple
1617

1718
import pdfplumber
1819
import pandas as pd
@@ -84,7 +85,7 @@ def extract_words_for_page(
8485

8586

8687
def load_pdf(
87-
filename: str,
88+
filename: Union[str, bytes, BinaryIO],
8889
load_images: bool = False,
8990
x_tolerance: int = 1.5,
9091
y_tolerance: int = 2,
@@ -99,7 +100,8 @@ def load_pdf(
99100
in a list of Layout objects with the original page order.
100101
101102
Args:
102-
filename (str): The path to the PDF file.
103+
filename (Union[str, bytes, BinaryIO]): The path to the PDF file,
104+
raw bytes, or a binary file-like object.
103105
load_images (bool, optional):
104106
Whether load screenshot for each page of the PDF file.
105107
When set to true, the function will return both the layout and
@@ -180,6 +182,10 @@ def load_pdf(
180182
>>> lp.draw_box(pdf_images[0], pdf_layout[0])
181183
"""
182184

185+
is_path = isinstance(filename, str)
186+
if isinstance(filename, bytes):
187+
filename = BytesIO(filename)
188+
183189
plumber_pdf_object = pdfplumber.open(filename)
184190

185191
all_page_layout = []
@@ -211,7 +217,12 @@ def load_pdf(
211217
else:
212218
import pdf2image
213219

214-
pdf_images = pdf2image.convert_from_path(filename, dpi=dpi)
220+
if is_path:
221+
pdf_images = pdf2image.convert_from_path(filename, dpi=dpi)
222+
else:
223+
if isinstance(filename, BytesIO):
224+
filename.seek(0)
225+
pdf_images = pdf2image.convert_from_bytes(filename.read() if hasattr(filename, 'read') else filename, dpi=dpi)
215226

216227
for page_id, page_image in enumerate(pdf_images):
217228
image_width, image_height = page_image.size

tests/test_io.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,29 @@ def test_pdf_closes_file_handle():
103103
load_pdf("dummy.pdf")
104104

105105
mock_pdf.__enter__.assert_called_once()
106-
mock_pdf.__exit__.assert_called_once()
106+
mock_pdf.__exit__.assert_called_once()
107+
108+
109+
def test_pdf_with_bytes():
110+
with open("tests/fixtures/io/example.pdf", "rb") as f:
111+
pdf_bytes = f.read()
112+
113+
pdf_layout = load_pdf(pdf_bytes)
114+
assert len(pdf_layout) == 1
115+
116+
page_layout = pdf_layout[0]
117+
for attr_name in ["width", "height", "index"]:
118+
assert attr_name in page_layout.page_data
119+
120+
assert len(set(ele.type for ele in page_layout)) == 3
121+
122+
123+
def test_pdf_with_file_object():
124+
with open("tests/fixtures/io/example.pdf", "rb") as f:
125+
pdf_layout = load_pdf(f)
126+
127+
assert len(pdf_layout) == 1
128+
129+
page_layout = pdf_layout[0]
130+
for attr_name in ["width", "height", "index"]:
131+
assert attr_name in page_layout.page_data

0 commit comments

Comments
 (0)