66import math
77import re
88import typing
9+ import warnings
910from collections .abc import Iterator
1011from enum import Enum
1112from pathlib import Path
@@ -375,14 +376,63 @@ def to_top_left_origin(self, page_height: float):
375376
376377
377378class PdfLine (ColorMixin , OrderedElement ):
378- """Model representing a line in a PDF document."""
379+ """Model representing a line in a PDF document.
380+
381+ .. deprecated::
382+ Use :class:`PdfShape` instead.
383+ """
379384
380385 parent_id : int
381386 points : list [Coord2D ]
382387 width : float = 1.0
383388
384389 coord_origin : CoordOrigin = CoordOrigin .BOTTOMLEFT
385390
391+ def __init__ (self , ** data ):
392+ """Initialize PdfLine with a deprecation warning."""
393+ warnings .warn (
394+ "PdfLine is deprecated, use PdfShape instead." ,
395+ DeprecationWarning ,
396+ stacklevel = 2 ,
397+ )
398+ super ().__init__ (** data )
399+
400+
401+ class PdfShape (OrderedElement ):
402+ """Model representing a vector shape in a PDF document."""
403+
404+ parent_id : int
405+ points : list [Coord2D ]
406+
407+ coord_origin : CoordOrigin = CoordOrigin .BOTTOMLEFT
408+
409+ # graphics state
410+ has_graphics_state : bool = False
411+
412+ line_width : float = - 1.0
413+ miter_limit : float = - 1.0
414+
415+ line_cap : int = - 1 # 0=butt, 1=round, 2=projecting square
416+ line_join : int = - 1 # 0=miter, 1=round, 2=bevel
417+
418+ dash_phase : float = 0.0
419+ dash_array : list [float ] = []
420+
421+ flatness : float = - 1.0
422+
423+ rgb_stroking : ColorRGBA = ColorRGBA (r = 0 , g = 0 , b = 0 , a = 255 )
424+ rgb_filling : ColorRGBA = ColorRGBA (r = 0 , g = 0 , b = 0 , a = 255 )
425+
426+ # deprecated — use rgb_stroking / rgb_filling instead
427+ rgba : Optional [ColorRGBA ] = Field (
428+ default = None ,
429+ deprecated = "Use `rgb_stroking` and `rgb_filling` instead." ,
430+ )
431+ width : Optional [float ] = Field (
432+ default = None ,
433+ deprecated = "Use `line_width` instead." ,
434+ )
435+
386436 def __len__ (self ) -> int :
387437 """Return the number of points in the line."""
388438 return len (self .points )
@@ -541,7 +591,11 @@ class SegmentedPdfPage(SegmentedPage):
541591 # Redefine typing to use PdfPageDimensions
542592 dimension : PdfPageGeometry
543593
544- lines : list [PdfLine ] = []
594+ lines : list [PdfLine ] = Field (
595+ default = [],
596+ deprecated = "Use `shapes` instead." ,
597+ )
598+ shapes : list [PdfShape ] = []
545599
546600 # Redefine typing of elements to include PdfTextCell
547601 char_cells : list [Union [PdfTextCell , TextCell ]]
@@ -713,10 +767,10 @@ def render_as_image(
713767 bitmap_resources_outline : str = "black" ,
714768 bitmap_resources_fill : str = "yellow" ,
715769 bitmap_resources_alpha : float = 1.0 ,
716- draw_lines : bool = True ,
717- line_color : str = "black" ,
718- line_width : int = 1 ,
719- line_alpha : float = 1.0 ,
770+ draw_shapes : bool = True ,
771+ shape_color : str = "black" ,
772+ shape_width : int = 1 ,
773+ shape_alpha : float = 1.0 ,
720774 draw_annotations : bool = True ,
721775 annotations_outline : str = "white" ,
722776 annotations_color : str = "green" ,
@@ -750,10 +804,10 @@ def render_as_image(
750804 bitmap_resources_outline: Outline color for bitmap resources
751805 bitmap_resources_fill: Fill color for bitmap resources
752806 bitmap_resources_alpha: Alpha value for bitmap resources
753- draw_lines : Whether to draw lines
754- line_color : Color for lines
755- line_width : Width for lines
756- line_alpha : Alpha value for lines
807+ draw_shapes : Whether to draw shapes
808+ shape_color : Color for shapes
809+ shape_width : Width for shapes
810+ shape_alpha : Alpha value for shapes
757811 draw_annotations: Whether to draw annotations
758812 annotations_outline: Outline color for annotations
759813 annotations_color: Fill color for annotations
@@ -771,7 +825,7 @@ def render_as_image(
771825 cell_bl_alpha ,
772826 cell_tr_alpha ,
773827 bitmap_resources_alpha ,
774- line_alpha ,
828+ shape_alpha ,
775829 annotations_alpha ,
776830 cropbox_alpha ,
777831 ]:
@@ -799,6 +853,15 @@ def render_as_image(
799853 bitmap_resources_alpha = bitmap_resources_alpha ,
800854 )
801855
856+ if draw_shapes :
857+ draw = self ._render_shapes (
858+ draw = draw ,
859+ page_height = page_height ,
860+ shape_color = shape_color ,
861+ shape_alpha = shape_alpha ,
862+ shape_width = shape_width ,
863+ )
864+
802865 if draw_cells_text :
803866 result = self ._render_cells_text (cell_unit = cell_unit , img = result , page_height = page_height )
804867
@@ -834,15 +897,6 @@ def render_as_image(
834897 cell_tr_radius = cell_tr_radius ,
835898 )
836899
837- if draw_lines :
838- draw = self ._render_lines (
839- draw = draw ,
840- page_height = page_height ,
841- line_color = line_color ,
842- line_alpha = line_alpha ,
843- line_width = line_width ,
844- )
845-
846900 return result
847901
848902 def _get_rgba (self , name : str , alpha : float ):
@@ -1107,37 +1161,46 @@ def _draw_cells_tr(
11071161
11081162 return draw
11091163
1110- def _render_lines (
1164+ def _render_shapes (
11111165 self ,
11121166 draw : ImageDraw .ImageDraw ,
11131167 page_height : float ,
1114- line_color : str ,
1115- line_alpha : float ,
1116- line_width : float ,
1168+ shape_color : str ,
1169+ shape_alpha : float ,
1170+ shape_width : float ,
11171171 ) -> ImageDraw .ImageDraw :
1118- """Render lines on the page.
1172+ """Render shapes on the page.
11191173
11201174 Args:
11211175 draw: PIL ImageDraw object
11221176 page_height: Height of the page
1123- line_color: Color for lines
1124- line_alpha : Alpha value for lines
1125- line_width: Width for lines
1177+ shape_color: Default color for shapes (used as fallback)
1178+ shape_alpha : Alpha value for shapes
1179+ shape_width: Default width for shapes (used as fallback)
11261180
11271181 Returns:
11281182 Updated ImageDraw object
11291183 """
1130- fill = self ._get_rgba (name = line_color , alpha = line_alpha )
1184+ # Draw each shape using its own stroking/filling colors
1185+ for shape in self .shapes :
1186+ shape .to_top_left_origin (page_height = page_height )
11311187
1132- # Draw each rectangle by connecting its four points
1133- for line in self .lines :
1134- line .to_top_left_origin (page_height = page_height )
1135- for segment in line .iterate_segments ():
1136- draw .line (
1137- (segment [0 ][0 ], segment [0 ][1 ], segment [1 ][0 ], segment [1 ][1 ]),
1138- fill = fill ,
1139- width = max (1 , round (line .width )),
1140- )
1188+ stroking_rgba = shape .rgb_stroking .as_tuple ()
1189+ filling_rgba = shape .rgb_filling .as_tuple ()
1190+
1191+ width = max (1 , round (shape .line_width )) if shape .line_width > 0 else max (1 , round (shape_width ))
1192+
1193+ # If the shape is closed (first and last points coincide), fill it
1194+ if len (shape .points ) >= 3 and shape .points [0 ] == shape .points [- 1 ]:
1195+ poly = [(p .x , p .y ) for p in shape .points ]
1196+ draw .polygon (poly , outline = stroking_rgba , fill = filling_rgba )
1197+ else :
1198+ for segment in shape .iterate_segments ():
1199+ draw .line (
1200+ (segment [0 ][0 ], segment [0 ][1 ], segment [1 ][0 ], segment [1 ][1 ]),
1201+ fill = stroking_rgba ,
1202+ width = width ,
1203+ )
11411204
11421205 return draw
11431206
0 commit comments