2017-02-25 13:23:51 -05:00
|
|
|
#!/usr/bin/env python
|
2021-12-09 22:27:51 -05:00
|
|
|
import logging
|
2020-11-29 15:45:04 +05:30
|
|
|
import os
|
2021-12-09 22:27:51 -05:00
|
|
|
import unittest
|
2017-02-25 13:23:51 -05:00
|
|
|
|
2021-12-09 22:27:51 -05:00
|
|
|
import pdfplumber
|
2020-12-16 22:19:17 -05:00
|
|
|
|
2017-02-25 13:23:51 -05:00
|
|
|
logging.disable(logging.ERROR)
|
|
|
|
|
|
|
|
|
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
|
|
|
|
|
|
|
2020-12-16 22:19:17 -05:00
|
|
|
class Test(unittest.TestCase):
|
2017-02-25 14:39:47 -05:00
|
|
|
def test_issue_13(self):
|
|
|
|
|
"""
|
2020-12-16 22:29:51 -05:00
|
|
|
Test slightly simplified from gist here:
|
|
|
|
|
https://github.com/jsvine/pdfplumber/issues/13
|
2017-02-25 14:39:47 -05:00
|
|
|
"""
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf = pdfplumber.open(
|
2017-02-25 14:39:47 -05:00
|
|
|
os.path.join(HERE, "pdfs/issue-13-151201DSP-Fond-581-90D.pdf")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Only find checkboxes this size
|
|
|
|
|
RECT_WIDTH = 9.3
|
|
|
|
|
RECT_HEIGHT = 9.3
|
|
|
|
|
RECT_TOLERANCE = 2
|
|
|
|
|
|
|
|
|
|
def filter_rects(rects):
|
2020-12-16 22:29:51 -05:00
|
|
|
# Just get the rects that are the right size to be checkboxes
|
2017-02-25 14:39:47 -05:00
|
|
|
rects_found = []
|
|
|
|
|
for rect in rects:
|
2020-12-16 22:19:17 -05:00
|
|
|
if (
|
|
|
|
|
rect["height"] > (RECT_HEIGHT - RECT_TOLERANCE)
|
|
|
|
|
and (rect["height"] < RECT_HEIGHT + RECT_TOLERANCE)
|
|
|
|
|
and (rect["width"] < RECT_WIDTH + RECT_TOLERANCE)
|
|
|
|
|
and (rect["width"] < RECT_WIDTH + RECT_TOLERANCE)
|
|
|
|
|
):
|
2017-02-25 14:39:47 -05:00
|
|
|
rects_found.append(rect)
|
|
|
|
|
return rects_found
|
|
|
|
|
|
2021-10-15 08:22:59 -04:00
|
|
|
def determine_if_checked(checkbox, checklines):
|
2020-12-16 22:29:51 -05:00
|
|
|
"""
|
|
|
|
|
This figures out if the bounding box of (either) line used to make
|
|
|
|
|
one half of the 'x' is the right size and overlaps with a rectangle.
|
|
|
|
|
This isn't foolproof, but works for this case.
|
|
|
|
|
It's not totally clear (to me) how common this style of checkboxes
|
|
|
|
|
are used, and whether this is useful approach to them.
|
|
|
|
|
Also note there should be *two* matching LTCurves for each checkbox.
|
|
|
|
|
But here we only test there's at least one.
|
|
|
|
|
"""
|
2017-02-25 14:39:47 -05:00
|
|
|
|
2021-10-15 08:22:59 -04:00
|
|
|
for cl in checklines:
|
2017-02-25 14:39:47 -05:00
|
|
|
|
2020-12-16 22:19:17 -05:00
|
|
|
if (
|
|
|
|
|
checkbox["height"] > (RECT_HEIGHT - RECT_TOLERANCE)
|
|
|
|
|
and (checkbox["height"] < RECT_HEIGHT + RECT_TOLERANCE)
|
|
|
|
|
and (checkbox["width"] < RECT_WIDTH + RECT_TOLERANCE)
|
|
|
|
|
and (checkbox["width"] < RECT_WIDTH + RECT_TOLERANCE)
|
|
|
|
|
):
|
2017-02-25 14:39:47 -05:00
|
|
|
|
|
|
|
|
xmatch = False
|
|
|
|
|
ymatch = False
|
|
|
|
|
|
2021-10-15 08:22:59 -04:00
|
|
|
if max(checkbox["x0"], cl["x0"]) <= min(checkbox["x1"], cl["x1"]):
|
2017-02-25 14:39:47 -05:00
|
|
|
xmatch = True
|
2021-10-15 08:22:59 -04:00
|
|
|
if max(checkbox["y0"], cl["y0"]) <= min(checkbox["y1"], cl["y1"]):
|
2017-02-25 14:39:47 -05:00
|
|
|
ymatch = True
|
|
|
|
|
if xmatch and ymatch:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
p0 = pdf.pages[0]
|
2021-10-15 08:22:59 -04:00
|
|
|
checklines = [
|
2021-02-07 16:33:54 -05:00
|
|
|
line
|
|
|
|
|
for line in p0.lines
|
|
|
|
|
if round(line["height"], 2) == round(line["width"], 2)
|
2021-10-15 08:22:59 -04:00
|
|
|
] # These are diagonals
|
2017-02-25 14:39:47 -05:00
|
|
|
rects = filter_rects(p0.objects["rect"])
|
|
|
|
|
|
2021-10-15 08:22:59 -04:00
|
|
|
n_checked = sum([determine_if_checked(rect, checklines) for rect in rects])
|
2017-02-25 14:39:47 -05:00
|
|
|
|
2020-12-16 22:19:17 -05:00
|
|
|
assert n_checked == 5
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2017-02-25 14:39:47 -05:00
|
|
|
|
2017-02-25 13:23:51 -05:00
|
|
|
def test_issue_14(self):
|
2020-12-16 22:19:17 -05:00
|
|
|
pdf = pdfplumber.open(os.path.join(HERE, "pdfs/cupertino_usd_4-6-16.pdf"))
|
2017-05-10 22:52:52 -04:00
|
|
|
assert len(pdf.objects)
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2017-02-25 13:23:51 -05:00
|
|
|
|
|
|
|
|
def test_issue_21(self):
|
2020-12-16 22:19:17 -05:00
|
|
|
pdf = pdfplumber.open(os.path.join(HERE, "pdfs/150109DSP-Milw-505-90D.pdf"))
|
2017-05-10 22:52:52 -04:00
|
|
|
assert len(pdf.objects)
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2017-05-10 22:52:52 -04:00
|
|
|
|
|
|
|
|
def test_issue_33(self):
|
2020-12-16 22:19:17 -05:00
|
|
|
pdf = pdfplumber.open(os.path.join(HERE, "pdfs/issue-33-lorem-ipsum.pdf"))
|
2017-05-10 22:52:52 -04:00
|
|
|
assert len(pdf.metadata.keys())
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2020-12-16 22:19:17 -05:00
|
|
|
|
2018-03-06 08:44:59 -05:00
|
|
|
def test_issue_53(self):
|
2020-12-16 22:19:17 -05:00
|
|
|
pdf = pdfplumber.open(os.path.join(HERE, "pdfs/issue-53-example.pdf"))
|
2018-03-06 08:44:59 -05:00
|
|
|
assert len(pdf.objects)
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2018-03-06 08:44:59 -05:00
|
|
|
|
2018-07-10 22:57:14 -04:00
|
|
|
def test_issue_67(self):
|
2020-12-16 22:19:17 -05:00
|
|
|
pdf = pdfplumber.open(os.path.join(HERE, "pdfs/issue-67-example.pdf"))
|
2018-07-10 22:57:14 -04:00
|
|
|
assert len(pdf.metadata.keys())
|
2020-07-26 15:27:51 -04:00
|
|
|
pdf.close()
|
2018-11-11 22:06:18 -05:00
|
|
|
|
2018-11-08 22:11:55 -05:00
|
|
|
def test_pr_88(self):
|
|
|
|
|
# via https://github.com/jsvine/pdfplumber/pull/88
|
|
|
|
|
path = os.path.join(HERE, "pdfs/pr-88-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
2018-11-11 22:06:18 -05:00
|
|
|
page = pdf.pages[0]
|
|
|
|
|
words = page.extract_words()
|
2018-11-08 22:11:55 -05:00
|
|
|
assert len(words) == 25
|
2018-11-11 22:06:18 -05:00
|
|
|
|
|
|
|
|
def test_issue_90(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-90-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
2020-12-16 22:29:51 -05:00
|
|
|
page.extract_words()
|
2018-11-11 22:06:18 -05:00
|
|
|
|
2019-08-29 19:05:23 -04:00
|
|
|
def test_pr_136(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/pr-136-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
2020-12-16 22:29:51 -05:00
|
|
|
page.extract_words()
|
2019-08-29 19:05:23 -04:00
|
|
|
|
2019-10-06 17:59:39 -04:00
|
|
|
def test_pr_138(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/pr-138-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
|
|
|
|
assert len(page.chars) == 5140
|
2020-12-16 22:19:17 -05:00
|
|
|
page.extract_tables(
|
|
|
|
|
{
|
|
|
|
|
"vertical_strategy": "explicit",
|
|
|
|
|
"horizontal_strategy": "lines",
|
|
|
|
|
"explicit_vertical_lines": page.curves + page.edges,
|
|
|
|
|
}
|
|
|
|
|
)
|
2019-10-06 17:59:39 -04:00
|
|
|
|
2019-10-06 17:41:31 -04:00
|
|
|
def test_issue_140(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-140-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
page = pdf.pages[0]
|
|
|
|
|
cropped_page = page.crop((0, 0, page.width, 122))
|
|
|
|
|
assert len(cropped_page.extract_table()) == 5
|
|
|
|
|
|
2020-04-28 23:55:24 -04:00
|
|
|
def test_issue_203(self):
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-203-decimalize.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
assert len(pdf.objects)
|
2020-05-27 22:23:22 -04:00
|
|
|
|
|
|
|
|
def test_issue_216(self):
|
|
|
|
|
"""
|
|
|
|
|
.extract_table() should return None if there's no table,
|
|
|
|
|
instead of crashing
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-140-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
cropped = pdf.pages[0].crop((0, 0, 1, 1))
|
|
|
|
|
assert cropped.extract_table() is None
|
2020-10-29 16:17:05 +05:30
|
|
|
|
|
|
|
|
def test_issue_297(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle integer type metadata
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-297-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
assert isinstance(pdf.metadata["Copies"], int)
|
2020-11-26 21:58:49 +05:30
|
|
|
|
|
|
|
|
def test_issue_316(self):
|
|
|
|
|
"""
|
|
|
|
|
Handle invalid metadata
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-316-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
2020-12-16 22:19:17 -05:00
|
|
|
assert (
|
|
|
|
|
pdf.metadata["Changes"][0]["CreationDate"] == "D:20061207105020Z00'00'"
|
|
|
|
|
)
|
2021-03-19 18:38:35 +00:00
|
|
|
|
|
|
|
|
def test_issue_386(self):
|
|
|
|
|
"""
|
|
|
|
|
util.extract_text() should not raise exception if given pure iterator
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/nics-background-checks-2015-11.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
chars = (char for char in pdf.chars)
|
|
|
|
|
pdfplumber.utils.extract_text(chars)
|
2021-10-15 09:34:39 -04:00
|
|
|
|
|
|
|
|
def test_issue_463(self):
|
|
|
|
|
"""
|
|
|
|
|
Extracting annotations should not raise UnicodeDecodeError on utf-16 text
|
|
|
|
|
"""
|
|
|
|
|
path = os.path.join(HERE, "pdfs/issue-463-example.pdf")
|
|
|
|
|
with pdfplumber.open(path) as pdf:
|
|
|
|
|
annots = pdf.annots
|
|
|
|
|
annots[0]["contents"] == "日本語"
|