From 216bedd00a4abf4452da332e6246dbc68ee69784 Mon Sep 17 00:00:00 2001 From: Jeremy Singer-Vine Date: Fri, 3 Feb 2023 12:15:55 -0500 Subject: [PATCH] Refactor handling of `pts` attribute In doing so, deprecate the `curve_obj["points"]` attribute, and fix `PageImage.draw_line(...)`'s handling of diagonal lines. --- CHANGELOG.md | 4 ++++ pdfplumber/display.py | 9 +++++++-- pdfplumber/page.py | 17 ++++++----------- pdfplumber/utils/geometry.py | 2 +- tests/test_convert.py | 6 +++--- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10fff72..f6f796f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format ## [Unreleased] +### Changed + +- Refactor handling of `pts` attribute and, in doing so, deprecate the `curve_obj["points"]` attribute, and fix `PageImage.draw_line(...)`'s handling of diagonal lines. + ### Development Changes - Convert `utils.py` into `utils/` submodules. Retains same interface, just an improvement in organization. ([6351d97](https://github.com/jsvine/pdfplumber/commit/6351d97)) diff --git a/pdfplumber/display.py b/pdfplumber/display.py index 58a7990..d8ba249 100644 --- a/pdfplumber/display.py +++ b/pdfplumber/display.py @@ -149,16 +149,21 @@ class PageImage: stroke: T_color = DEFAULT_STROKE, stroke_width: int = DEFAULT_STROKE_WIDTH, ) -> "PageImage": + # If passing a raw list of points, use those if isinstance(points_or_obj, (tuple, list)): points = points_or_obj - elif isinstance(points_or_obj, dict) and "points" in points_or_obj: - points = points_or_obj["points"] + # Else, use the "pts" attribute if available + elif isinstance(points_or_obj, dict) and "pts" in points_or_obj: + points = [(x, y) for x, y in points_or_obj["pts"]] + # Otherwise, just use ((x0, top), (x1, bottom)) else: obj = points_or_obj points = ((obj["x0"], obj["top"]), (obj["x1"], obj["bottom"])) + self.draw.line( list(map(self._reproject, points)), fill=stroke, width=stroke_width ) + return self def draw_lines( diff --git a/pdfplumber/page.py b/pdfplumber/page.py index b2f3d42..8d37ad4 100644 --- a/pdfplumber/page.py +++ b/pdfplumber/page.py @@ -18,11 +18,8 @@ from pdfminer.layout import ( LTChar, LTComponent, LTContainer, - LTCurve, LTItem, - LTLine, LTPage, - LTRect, LTTextContainer, ) from pdfminer.pdfinterp import PDFPageInterpreter @@ -196,6 +193,9 @@ class Page(Container): self._objects: Dict[str, T_obj_list] = self.parse_objects() return self._objects + def point2coord(self, pt: Tuple[T_num, T_num]) -> Tuple[T_num, T_num]: + return (pt[0], self.height - pt[1]) + def process_object(self, obj: LTItem) -> T_obj: kind = re.sub(lt_pat, "", obj.__class__.__name__).lower() @@ -220,15 +220,10 @@ class Page(Container): attr["stroking_color"] = gs.scolor attr["non_stroking_color"] = gs.ncolor - if isinstance(obj, LTCurve) and not isinstance(obj, (LTRect, LTLine)): + if "pts" in attr: + attr["pts"] = list(map(self.point2coord, attr["pts"])) - def point2coord(pt: Tuple[T_num, T_num]) -> Tuple[T_num, T_num]: - x, y = pt - return (x, self.height - y) - - attr["points"] = list(map(point2coord, obj.pts)) - - if attr.get("y0") is not None: + if "y0" in attr: attr["top"] = self.height - attr["y1"] attr["bottom"] = self.height - attr["y0"] attr["doctop"] = self.initial_doctop + attr["top"] diff --git a/pdfplumber/utils/geometry.py b/pdfplumber/utils/geometry.py index 8a08597..6ffd7b6 100644 --- a/pdfplumber/utils/geometry.py +++ b/pdfplumber/utils/geometry.py @@ -190,7 +190,7 @@ def resize_object(obj: T_obj, key: str, value: T_num) -> T_obj: def curve_to_edges(curve: T_obj) -> T_obj_list: - point_pairs = zip(curve["points"], curve["points"][1:]) + point_pairs = zip(curve["pts"], curve["pts"][1:]) return [ { "x0": min(p0[0], p1[0]), diff --git a/tests/test_convert.py b/tests/test_convert.py index 73baa68..fcbe8a6 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -70,7 +70,7 @@ class Test(unittest.TestCase): assert c.split("\r\n")[9] == ( "char,1,45.83,58.826,656.82,674.82,117.18,117.18,135.18,12.996," '18.0,12.996,,,,,,TimesNewRomanPSMT,,,"(1, 0, 0, 1, 45.83, 660.69)"' - ',,"(0, 0, 0)",,,18.0,,,,,Y,,1,' + ',,"(0, 0, 0)",,18.0,,,,,Y,,1,' ) io = StringIO() @@ -125,7 +125,7 @@ class Test(unittest.TestCase): assert res.decode("utf-8").split("\r\n")[9] == ( "char,1,45.83,58.826,656.82,674.82,117.18,117.18,135.18,12.996," '18.0,12.996,,,,,,TimesNewRomanPSMT,,,"(1, 0, 0, 1, 45.83, 660.69)"' - ',,"(0, 0, 0)",,,18.0,,,,,Y,,1,' + ',,"(0, 0, 0)",,18.0,,,,,Y,,1,' ) def test_cli_csv_exclude(self): @@ -147,7 +147,7 @@ class Test(unittest.TestCase): assert res.decode("utf-8").split("\r\n")[9] == ( "char,1,45.83,58.826,656.82,674.82,117.18,117.18,135.18,12.996," "18.0,12.996,,,,,,TimesNewRomanPSMT,," - ',,"(0, 0, 0)",,,18.0,,,,,Y,,1,' + ',,"(0, 0, 0)",,18.0,,,,,Y,,1,' ) def test_cli_csv_include(self):