ó
    /j-jÔj  ã                   ó0  • S r SSKrSSKrSSKJr  SSKJr  SSKJr  SrS r	 S#S jr
     S$S	 jrS
 rS rS%S jrS rS rS rSrSrSrS rS rS&S jr\4S jrS rS rSrSr\4S jr " S S5      rS rS r \4S jr!\4S  jr"\4S! jr#Sr$\$4S" jr%g)'zpFunctions useful for detecting graphical elements from the image to using OpenCV to reconstruct / detect tables.é    N)ÚLTContainer)ÚLTLine)ÚLTRectgü©ñÒMb@?c                 ó²   • US:X  a%  [         R                  " U [         R                  5      $ US:X  a%  [         R                  " U [         R                  5      $ U $ )aJ  Undo rotation of an image extracted from a PDF.

Parameters
----------
pdf_image: numpy.ndarray representing the image.
rotation: str
   Either "" (no rotation), "clockwise", or "anticlockwise".  The
   **inverse** of this rotation will be applied to the image.

Returns
-------
img: numpy.ndarray representing the rotated image.
Ú	clockwiseÚanticlockwise)Úcv2ÚrotateÚROTATE_90_COUNTERCLOCKWISEÚROTATE_90_CLOCKWISE)Ú	pdf_imageÚrotations     ÚU/home/mande/repo/quber/.venv/lib/python3.13/site-packages/camelot/image_processing.pyÚundo_rotationr      sH   € ð �;ÓÜ�zŠz˜)¤S×%CÑ%CÓDÐDØ�?Ó"Ü�zŠz˜)¤S×%<Ñ%<Ó=Ð=ØÐó    c                 ó|  • [        U [        R                  5      (       a  U O[        R                  " U 5      n[        XT5      n[        R                  " U[        R                  5      nU(       d  [        R                  " U5      n[        R                  " US[        R                  [        R                  X#5      nXW4$ )u1  Thresholds an image using OpenCV's adaptiveThreshold.

Parameters
----------
imagename : str or numpy.ndarray
    Path to an image file, or an already-decoded BGR image array (the
    latter lets callers skip the PNG round-trip â€” see #40).
process_background : bool, optional (default: False)
    Whether or not to process lines that are in background.
blocksize : int, optional (default: 15)
    Size of a pixel neighborhood that is used to calculate a
    threshold value for the pixel: 3, 5, 7, and so on.

    For more information, refer `OpenCV's adaptiveThreshold
    <https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#adaptivethreshold>`_.
c : int, optional (default: -2)
    Constant subtracted from the mean or weighted mean.
    Normally, it is positive but may be zero or negative as well.

    For more information, refer `OpenCV's adaptiveThreshold
    <https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#adaptivethreshold>`_.
rotation: str, optional (default: "")
    Either "" (no rotation), "clockwise", or "anticlockwise".  The
    **inverse** of this rotation will be applied to the image.

Returns
-------
img : object
    numpy.ndarray representing the original image.
threshold : object
    numpy.ndarray representing the thresholded image.
éÿ   )Ú
isinstanceÚnpÚndarrayr	   Úimreadr   ÚcvtColorÚCOLOR_BGR2GRAYÚinvertÚadaptiveThresholdÚADAPTIVE_THRESH_GAUSSIAN_CÚTHRESH_BINARY)Ú	imagenameÚprocess_backgroundÚ	blocksizeÚcr   ÚimgÚgrayÚ	thresholds           r   Úadaptive_thresholdr%   (   s†   € ôF " )¬R¯Z©Z×8Ñ8‰)¼c¿jºjÈÓ>S€CÜ
˜Ó
&€CÜ�<Š<˜œS×/Ñ/Ó0€DÞÜ�yŠy˜‹ˆÜ×%Ò%Øˆc”3×1Ñ1´3×3DÑ3DÀió€Ið ˆ>Ðr   c                 ó0  • US;  a  [        S5      e[        XU5      u  pg[        X5      n [        XXE5      n[        R
                  " UR                  [        R                  5      [        R                  [        R                  5      u  pš[        X’5      nX‹4$ )u  
Finds horizontal and vertical lines by applying morphological transformations on an image.

Parameters
----------
threshold : object
    numpy.ndarray representing the thresholded image.
regions : list, optional (default: None)
    List of page regions that may contain tables of the form x1,y1,x2,y2
    where (x1, y1) -> left-top and (x2, y2) -> right-bottom
    in image coordinate space.
direction : string, optional (default: 'horizontal')
    Specifies whether to find vertical or horizontal lines.
line_scale : int, optional (default: 40)
    Factor by which the page dimensions will be divided to get
    smallest length of lines that should be detected.
iterations : int, optional (default: 0)
    Number of dilation passes applied to close small gaps in the
    line mask. Useful for tables whose ruled lines don't quite
    meet at corners.
erode_iterations : int, optional (default: 0)
    Number of erosion passes applied **after** dilation. Set equal
    to ``iterations`` to perform morphological *closing* (dilate
    then erode of equal count): gaps are closed without enlarging
    the line mask overall. Requested in #363 â€” previously the
    erode step was missing, so ``iterations>=1`` widened every
    line and added phantom top/bottom lines around tables.

Returns
-------
dmask : object
    numpy.ndarray representing pixels where vertical/horizontal
    lines lie.
lines : list
    List of tuples representing vertical/horizontal lines with
    coordinates relative to a left-top origin in
    image coordinate space.
)ÚverticalÚ
horizontalz6Specify direction as either 'vertical' or 'horizontal')Ú
ValueErrorÚcreate_structuring_elementÚapply_region_maskÚprocess_imager	   ÚfindContoursÚastyper   Úuint8ÚRETR_EXTERNALÚCHAIN_APPROX_SIMPLEÚextract_lines_from_contours)r$   ÚregionsÚ	directionÚ
line_scaleÚ
iterationsÚerode_iterationsÚelÚsizeÚprocessed_thresholdÚcontoursÚ_Úliness               r   Ú
find_linesr>   V   sˆ   € ð\ Ð2Ó2ÜÐQÓRÐRä)¨)À
ÓK�H€BÜ! )Ó5€Iä'¨	°zÓTÐÜ×"Ò"Ø×"Ñ"¤2§8¡8Ó,¬c×.?Ñ.?Ä×AXÑAXó�K€Hô (¨Ó<€EàÐ%Ð%r   c                 óþ   • US:X  a<  U R                   S   U-  n[        R                  " [        R                  SU45      nXC4$ U R                   S   U-  n[        R                  " [        R                  US45      nXC4$ )ar  
Create a structuring element based on the specified direction.

Parameters
----------
threshold : object
    numpy.ndarray representing the thresholded image.
direction : string
    Direction to create the structuring element.
line_scale : int
    Factor for scaling the size of the structuring element.

Returns
-------
tuple
    The structuring element and its size.
r'   r   é   )Úshaper	   ÚgetStructuringElementÚ
MORPH_RECT)r$   r4   r5   r9   r8   s        r   r*   r*   ”   sx   € ð$ �JÓØ�‰˜qÑ! ZÑ/ˆÜ×&Ò&¤s§~¡~¸¸4°yÓAˆð
 ˆ8€Oð �‰˜qÑ! ZÑ/ˆÜ×&Ò&¤s§~¡~¸¸a°yÓAˆàˆ8€Or   c                 óÊ   • Ub_  [         R                  " U R                  [         R                  S9nU H  nUu  pEpgSX%XW-   2XDU-   24'   M     [         R                  " X5      n U $ )a  
Apply a mask to the threshold image based on specified regions.

Parameters
----------
threshold : object
    numpy.ndarray representing the thresholded image.
regions : list
    List of regions to apply the mask.

Returns
-------
numpy.ndarray
    The masked threshold image.
)Údtyper@   )r   ÚzerosrA   r/   Úmultiply)r$   r3   Úregion_maskÚregionÚxÚyÚwÚhs           r   r+   r+   °   sd   € ð  ÑÜ—h’h˜yŸ™´b·h±hÑ?ˆÛˆFØ‰JˆA�!Ø01ˆK˜A™E˜	 1¨1¡u 9Ð,Ó-ñ ô —K’K 	Ó7ˆ	àÐr   c                 óÀ   • [         R                  " X5      n [         R                  " X5      n [         R                  " XUS9nU(       a  [         R                  " XAUS9nU$ )u€  
Apply morphological operations to the threshold image.

Parameters
----------
threshold : object
    numpy.ndarray representing the thresholded image.
el : object
    Structuring element for morphological operations.
iterations : int
    Number of dilation passes applied to close small gaps in the
    line mask.
erode_iterations : int, optional (default: 0)
    Number of erosion passes applied *after* the dilation. When
    equal to ``iterations`` this is a morphological closing â€”
    gaps in the lines are bridged without thickening the mask
    overall. See #363.

Returns
-------
numpy.ndarray
    The processed threshold image.
)r6   )r	   ÚerodeÚdilate)r$   r8   r6   r7   Údmasks        r   r,   r,   Ê   sI   € ô0 —	’	˜)Ó(€IÜ—
’
˜9Ó)€IÜ�JŠJ�y°Ñ<€EÞÜ—	’	˜%Ð0@ÑAˆà€Lr   c                 óþ   • / nU  Ht  n[         R                  " U5      u  pEpgXDU-   p˜XUU-   pºUS:X  a!  UR                  X‰-   S-  X¸U	-   S-  U
45        MM  US:X  d  MU  UR                  XŠU-   S-  XšU-   S-  45        Mv     U$ )a5  
Extract lines from contours based on the specified direction.

Parameters
----------
contours : list
    List of contours found in the image.
direction : string
    Specifies whether to extract vertical or horizontal lines.

Returns
-------
list
    List of tuples representing the coordinates of the lines.
r'   é   r(   )r	   ÚboundingRectÚappend)r;   r4   r=   r!   rJ   rK   rL   rM   Úx1Úx2Úy1Úy2s               r   r2   r2   ë   s’   € ð  €EÛˆÜ×%Ò% aÓ(‰
ˆˆaØ˜‘EˆBØ˜‘EˆBØ˜
Ó"Ø�L‰L˜2™7 q™.¨"°B©w¸1©n¸bÐAÖBØ˜,Õ&Ø�L‰L˜" B™w¨1™n¨b¸±7¸q±.ÐAÖBñ ð €Lr   c                 ó^  • X-   n[         R                  " UR                  [        R                  5      [         R
                  [         R                  5      u  p4[        UR                  S   UR                  S   -  5      =(       d    SnU[        -  n[        U[         R                  SS9nU Vs/ s H!  n[         R                  " U5      U:¼  d  M  UPM#     nn/ nU HG  n[         R                  " USS5      n	[         R                  " U	5      u  p«pÍUR                  X«XÍ45        MI     U$ s  snf )aÁ  Find table boundaries using OpenCV's findContours.

Parameters
----------
vertical : object
    numpy.ndarray representing pixels where vertical lines lie.
horizontal : object
    numpy.ndarray representing pixels where horizontal lines lie.

Returns
-------
cont : list
    List of tuples representing table boundaries. Each tuple is of
    the form (x, y, w, h) where (x, y) -> left-top, w -> width and
    h -> height in image coordinate space.

r   r@   g      ð?T)ÚkeyÚreverseé   )r	   r-   r.   r   r/   r0   r1   ÚfloatrA   Ú_MIN_TABLE_AREA_FRACTIONÚsortedÚcontourAreaÚapproxPolyDPrT   rU   )r'   r(   Úmaskr;   Ú__Ú	page_areaÚmin_arear!   ÚcontÚc_polyrJ   rK   rL   rM   s                 r   Úfind_contoursri     sù   € ð$ Ñ €Dä×#Ò#Ø�‰”B—H‘HÓœs×0Ñ0´#×2IÑ2Ió�L€Hô �d—j‘j ‘m d§j¡j°¡mÑ3Ó4×;¸€IØÔ3Ñ3€HÜ�h¤C§O¡O¸TÑB€HÙ#ÓFš8�a¤s§¢°qÓ'9¸XÑ'E—™8€HÐFà€DÛˆÜ×!Ò! ! Q¨Ó-ˆÜ×%Ò% fÓ-‰
ˆˆaØ�‰�Q˜1�LÖ!ñ ð €Kùò Gs   Â/D*ÃD*c                 óø  • [         R                  " X5      n0 nU  HÛ  nUu  pgp‰X7Xy-   2XfU-   24   n
[        R                  " U
R	                  [         R
                  5      [        R                  [        R                  5      u  p¼[        U5      S::  a  Mz  / nU HM  n[        R                  " U5      u  nnnnUSU-  U-   S-  -   USU-  U-   S-  -   nnUR                  UU45        MO     XÔXgU	-   Xh-   U4'   MÝ     U$ )aµ  Find joints/intersections present inside each table boundary.

Parameters
----------
contours : list
    List of tuples representing table boundaries. Each tuple is of
    the form (x, y, w, h) where (x, y) -> left-top, w -> width and
    h -> height in image coordinate space.
vertical : object
    numpy.ndarray representing pixels where vertical lines lie.
horizontal : object
    numpy.ndarray representing pixels where horizontal lines lie.

Returns
-------
tables : dict
    Dict with table boundaries as keys and list of intersections
    in that boundary as their value.
    Keys are of the form (x1, y1, x2, y2) where (x1, y1) -> lb
    and (x2, y2) -> rt in image coordinate space.

é   rS   )r   rG   r	   r-   r.   r/   Ú
RETR_CCOMPr1   ÚlenrT   rU   )r;   r'   r(   ÚjointsÚtablesr!   rJ   rK   rL   rM   ÚroiÚjcrd   Újoint_coordsÚjÚjxÚjyÚjwÚjhÚc1Úc2s                        r   Úfind_jointsrz   0  s  € ô. �[Š[˜Ó.€FØ€FÛˆØ‰
ˆˆaØ˜™�Y ¨¡E 	Ð)Ñ*ˆÜ×!Ò!Ø�J‰J”r—x‘xÓ ¤#§.¡.´#×2IÑ2Ió
‰ˆô ˆr‹7�a‹<ÙØˆÛˆAÜ ×-Ò-¨aÓ0‰NˆB��B˜Ø˜!˜b™& 2™+¨!Ñ+Ñ+¨Q°!°b±&¸2±+À!Ñ1CÑ-C�ˆBØ×Ñ  R Ö)ñ ð (4��q‘5˜!™% Ð#Ó$ñ ð €Mr   g      à?ç       @g      ø?c              #   ó~   #   • U  H1  nUv •  [        U[        5      (       d  M  [        U5       Sh  v•N   M3     g N	7f)z9Recursively yield every object in an LTContainer subtree.N)r   r   Ú_walk_layout_objects)Ú	containerÚobjs     r   r}   r}   u  s4   é € ãˆØŠ	Ü�cœ;×'Ó'Ü+¨CÓ0×0Ò0ò ñ 1ùs   ‚=£=±;²
=c                 óJ  • / n[        U 5       GH�  n[        U[        5      (       aQ  [        USS5      (       a?  UR	                  UR
                  UR                  UR                  UR                  45        Mj  [        U[        5      (       d  M�  [        USS5      n[        USS5      nU(       a÷  UR	                  UR
                  UR                  UR                  UR                  45        UR	                  UR
                  UR                  UR                  UR                  45        UR	                  UR
                  UR                  UR
                  UR                  45        UR	                  UR                  UR                  UR                  UR                  45        GM™  U(       d  GM£  UR                  UR
                  -
  nUR                  UR                  -
  n[        XV5      [        ::  d  GMë  [        XV5      [        :”  d  GM  XV:¼  aG  UR                  UR                  -   S-  nUR	                  UR
                  XrR                  U45        GMM  UR
                  UR                  -   S-  nUR	                  X‚R                  X‚R                  45        GM“     U$ )aG  Collect ruled line segments from a layout tree (PDF coord space).

Returns a list of ``(x0, y0, x1, y1)`` tuples. Both stroked LTLines
and the four edges of stroked LTRects are emitted. Filled-but-not-
stroked LTRects with one narrow dimension are also treated as lines
(covers PDFs that draw rules as thin filled rectangles).
ÚstrokeTFÚfillrS   )r}   r   r   ÚgetattrrU   Úx0Úy0rV   rX   r   ÚminÚ_LINE_AS_THIN_RECT_TOLÚmax)	ÚlayoutÚoutr   ÚstrokedÚfilledÚwidthÚheightÚmid_yÚmid_xs	            r   Ú_ruled_lines_from_layoutr‘   }  sÇ  € ð 46€CÜ# F×+ˆÜ�cœ6×"Ñ"¤w¨s°H¸d×'CÑ'CØ�J‰J˜Ÿ™ §¡¨¯©°·±Ð7Ö8Ü˜œV×$Ó$Ü˜c 8¨UÓ3ˆGÜ˜S &¨%Ó0ˆFÞà—
‘
˜CŸF™F C§F¡F¨C¯F©F°C·F±FÐ;Ô<Ø—
‘
˜CŸF™F C§F¡F¨C¯F©F°C·F±FÐ;Ô<Ø—
‘
˜CŸF™F C§F¡F¨C¯F©F°C·F±FÐ;Ô<Ø—
‘
˜CŸF™F C§F¡F¨C¯F©F°C·F±FÐ;×<ß‘àŸ™ §¡™�ØŸ™ #§&¡&™�ä˜Ó&Ô*@Ö@Ü˜EÓ*Ô-CÖCà“Ø!$§¡¨#¯&©&¡°AÑ 5˜ØŸ
™
 C§F¡F¨E·6±6¸5Ð#A×Bà!$§¡¨#¯&©&¡°AÑ 5˜ØŸ
™
 E¯6©6°5¿&¹&Ð#A×Bñ3 ,ð4 €Jr   c           
      ó¤  • US;  a  [        S5      e[        U 5      n/ nU H¢  u  pEpg[        Xd-
  5      n[        Xu-
  5      n	US:X  a9  U	[        ::  a/  US:”  a)  UR	                  [        XF5      U[        XF5      U45        M_  US:X  d  Mg  U[        ::  d  Ms  U	S:”  d  M{  UR	                  U[        XW5      U[        XW5      45        M¤     [        X15      $ )u   Return ruled lines from a playa layout tree (Stage 1 of #763).

Drop-in companion to :func:`find_lines` for ``flavor='lattice'``'s
vector engine. Walks ``layout`` (recursively), classifies each
LTLine / stroked LTRect / thin filled LTRect into horizontal or
vertical based on its dominant axis, and returns the subset matching
``direction``.

Parameters
----------
layout : object
    An ``LTPage`` / ``LTContainer`` from ``playa`` â€” typically what
    the lattice parser already stores on ``self.layout``.
direction : str, optional (default: 'horizontal')
    ``'horizontal'`` returns lines whose y-delta is below the
    orthogonality tolerance; ``'vertical'`` returns those whose
    x-delta is below it.

Returns
-------
lines : list[tuple[float, float, float, float]]
    Each tuple is ``(x0, y0, x1, y1)`` in PDF coordinate space â€”
    same shape as :func:`find_lines`'s second return value, but in
    PDF coords (not image coords). The Lattice integration in
    Stage 2 will apply the existing ``scale_pdf`` to convert.

Notes
-----
The output drops the morphological mask :func:`find_lines` produces
as its first return; the vector engine doesn't need it because it
bypasses :func:`find_contours` (which is the only consumer of the
mask) and computes table bboxes from line intersections directly.
©r(   r'   ú,direction must be 'horizontal' or 'vertical'r(   r   r'   )r)   r‘   ÚabsÚ_LINE_ORTHOGONAL_TOLrU   r†   rˆ   Úcoalesce_collinear_lines)
r‰   r4   ÚrawÚresultr„   r…   rV   rX   ÚdxÚdys
             r   Úfind_lines_from_layoutrœ   £  sÀ   € ðD Ð2Ó2ÜÐGÓHÐHÜ
" 6Ó
*€CØ68€FÛ‰ˆ�Ü�‘‹\ˆÜ�‘‹\ˆØ˜Ó$¨Ô/CÓ)CÈÈQËØ�M‰Mœ3˜r›;¨¬C°«K¸Ð<Ö=Ø˜*Õ$¨Ô/CÕ)CÈÈQÍØ�M‰M˜2œs 2›{¨B´°B³Ð<Ö=ñ ô $ FÓ6Ð6r   c                 ó  • US;  a  [        S5      eU (       d  / $ [        X5      n/ nU Hk  u  pVnU(       aF  XTS   S   -
  U::  a8  US   S   R                  U5        US   S   R                  Xg45        XTS   S'   MS  UR                  U/Xg4/US.5        Mm     / nU H^  n	[        U	S   5      [	        U	S   5      -  n[        [        U	S   5      U5       H"  u  pgUR                  US:X  a  XeXu4OXVXW45        M$     M`     U$ )	a|  Merge near-collinear ruled-line segments into single lines.

PDFs frequently draw one logical rule as a chain of short sub-segments;
left unmerged they make :func:`find_joints_from_lines` under-count
joints and miss grids. Segments are grouped by their constant axis
(within ``tol``) and, within a group, those whose extents overlap or
sit within ``tol`` of each other are fused.

Parameters
----------
lines : list[tuple[float, float, float, float]]
    ``(x0, y0, x1, y1)`` segments, as produced by
    :func:`find_lines_from_layout`.
direction : str
    ``'horizontal'`` (segments share a constant ``y``, merge along
    ``x``) or ``'vertical'`` (constant ``x``, merge along ``y``).
tol : float, optional
    Merge tolerance in PDF units (default :data:`_LINE_COALESCE_TOL`).

Returns
-------
list[tuple[float, float, float, float]]
    The merged segments, same tuple shape as the input.
r“   r”   éÿÿÿÿÚlastÚconstsÚ	intervals)r    r¡   rŸ   r(   )r)   Ú_coalesce_normaliserU   Úsumrm   Ú_merge_spansr`   )
r=   r4   ÚtolÚnormÚclustersÚconstÚloÚhirŠ   Úclusters
             r   r—   r—   Ó  s&  € ð2 Ð2Ó2ÜÐGÓHÐHÞØˆ	ä˜uÓ0€Dð €HÛ‰ˆ�2Þ˜¨¡¨VÑ 4Ñ4¸Ó;Ø�R‰L˜Ñ"×)Ñ)¨%Ô0Ø�R‰L˜Ñ%×,Ñ,¨b¨XÔ6Ø#(�R‰L˜Ó à�O‰O¨ w¸r¸h¸ZÐQVÑWÖXñ ð €CÛˆÜ�G˜HÑ%Ó&¬¨W°XÑ->Ó)?Ñ?ˆÜ"¤6¨'°+Ñ*>Ó#?ÀÖE‰FˆBØ�J‰Jà Ó,ð ˜BÑ&à Ð+öó Fñ ð €Jr   c                 óÆ   • / nU  HH  u  p4pVUS:X  a  XF-   S-  nX5::  a  X54OXS4u  p‰OX5-   S-  nXF::  a  XF4OXd4u  p‰UR                  XxU	45        MJ     UR                  5         U$ )zBReduce each segment to ``(const_axis, span_lo, span_hi)``, sorted.r(   r{   )rU   Úsort)
r=   r4   r¦   r„   r…   rV   rX   r¨   r©   rª   s
             r   r¢   r¢   	  sw   € à€DÛ‰ˆ�Ø˜Ó$Ø‘W ‘OˆEØ!#£�b‘X°¨x‰FˆB�à‘W ‘OˆEØ!#£�b‘X°¨x‰FˆBØ�‰�U �OÖ$ñ  ð 	‡I�I„KØ€Kr   c                 ó´   • [        U S   5      /nU SS  H?  u  p4X2S   S   U-   ::  a  [        US   S   U5      US   S'   M-  UR                  X4/5        MA     U$ )z:Merge overlapping / within-``tol`` ``(lo, hi)`` intervals.r   r@   Nrž   )Úlistrˆ   rU   )r¡   r¥   Úmergedr©   rª   s        r   r¤   r¤     si   € ä�9˜Q‘<Ó Ð!€FØ˜A˜B“-‰ˆØ˜‘˜A‘ Ñ$Ó$Ü  r¡
¨1¡¨rÓ2ˆF�2‰J�q‹Mà�M‰M˜2˜(Ö#ñ	  ð
 €Mr   rk   c                 ó¢   • U u  p4pVUu  pxpiX5::  a  X54OXS4u  p«X‰::  a  X‰4OX˜4u  pÍX¢-
  Us=::  a  X²-   ::  a  O  gXÂ-
  Us=::  a  XÒ-   ::  a   Xt4$   gg)a’  Return the (x, y) crossing of a horizontal and vertical line, or None.

``h_line`` is ``(x0, y, x1, y)`` (constant y), ``v_line`` is
``(x, y0, x, y1)`` (constant x), both in PDF coords as produced by
:func:`find_lines_from_layout`. They cross when the vertical line's x
falls within the horizontal line's x-span and the horizontal line's y
falls within the vertical line's y-span, each within ``tol``.
N© )Úh_lineÚv_liner¥   Úhx0ÚhyÚhx1r<   ÚvxÚvy0Úvy1Úx_loÚx_hiÚy_loÚy_his                 r   Ú_line_crossingr¿   .  so   € ð �O€CˆSØ�O€BˆQØ"›z�#‘°¨z�J€DØ"›z�#‘°¨z�J€DØ‰
�rÕ)˜d™jÕ)àð 04©z¸bÕ.PÀTÁZÔ.PØˆxˆð /Qár   c                   ó*   • \ rS rSrSrS rS rS rSrg)Ú
_UnionFindi@  zDMinimal union-find with path-halving, for clustering crossing lines.c                 ó6   • [        [        U5      5      U l        g ©N)r¯   ÚrangeÚparent)ÚselfÚns     r   Ú__init__Ú_UnionFind.__init__C  s   € Üœ5 ›8“nˆ�r   c                 óÆ   • U R                   U   U:w  aM  U R                   U R                   U      U R                   U'   U R                   U   nU R                   U   U:w  a  MM  U$ rÃ   ©rÅ   )rÆ   Úis     r   ÚfindÚ_UnionFind.findF  sV   € Ø�k‰k˜!‰n Ó!Ø!Ÿ[™[¨¯©°Q©Ñ8ˆD�K‰K˜‰NØ—‘˜A‘ˆAð �k‰k˜!‰n Õ!ð ˆr   c                 ón   • U R                  U5      U R                  U5      pCX4:w  a  X@R                  U'   g g rÃ   )rÍ   rÅ   )rÆ   ÚaÚbÚraÚrbs        r   ÚunionÚ_UnionFind.unionL  s.   € Ø—‘˜1“˜tŸy™y¨›|ˆBØ‹8Ø �K‰K˜ŠOð r   rË   N)	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__rÈ   rÍ   rÔ   Ú__static_attributes__r²   r   r   rÁ   rÁ   @  s   † ÙNò%òõ!r   rÁ   c                 óô   • [        U 5      n[        U[        U5      -   5      n0 n[        U 5       HC  u  pg[        U5       H/  u  p‰[        XyU5      n
U
c  M  UR	                  XcU-   5        X¥Xh4'   M1     ME     XE4$ )a&  Union-find over crossing h/v lines.

Returns ``(uf, crossings)`` where ``uf`` is a :class:`_UnionFind`
spanning ``[0, n_h)`` horizontal + ``[n_h, n_h+n_v)`` vertical line
indices, joined wherever an h-line crosses a v-line, and
``crossings`` maps ``(h_idx, v_idx)`` to the ``(x, y)`` crossing.
)rm   rÁ   Ú	enumerater¿   rÔ   )Úhorizontal_linesÚvertical_linesr¥   Ún_hÚufÚ	crossingsrª   rM   ÚviÚvÚpts              r   Ú_line_crossingsræ   R  s€   € ô ÐÓ
€CÜ	�Cœ#˜nÓ-Ñ-Ó	.€BØ€IÜÐ+Ö,‰ˆÜ˜~Ö.‰EˆBÜ  cÓ*ˆBØ‹~Ø—‘˜ 2™XÔ&Ø&(˜2˜(Ó#ó	 /ñ -ð ˆ=Ðr   c                 ó�  • U  VVs/ s H  o3S   US   4  H  oDPM     M     snnU Vs/ s H  o3S   PM	     sn-   nU VVs/ s H  o3S   US   4  H  oDPM     M     snnU  Vs/ s H  o3S   PM	     sn-   nU(       a  U(       d  g[        U5      [        U5      [        U5      [        U5      4U4$ s  snnf s  snf s  snnf s  snf )zDBounding box ``((x0, y0, x1, y1), joints)`` over a cluster, or None.r   rS   r@   r]   N)r†   rˆ   )ÚhsÚvsrn   Úlnr!   ÚxsÚyss          r   Ú_cluster_bboxrí   f  s´   € áÔ	1’b� a¡5¨"¨Q©%¤.˜QŠ!¡.‰!‘bÒ	1ÁRÓ4HÂR¸r¸´UÁRÑ4HÑ	H€BÙÔ	1’b� a¡5¨"¨Q©%¤.˜QŠ!¡.‰!‘bÒ	1ÁRÓ4HÂR¸r¸´UÁRÑ4HÑ	H€BÞ–RØÜ�‹W”c˜"“gœs 2›w¬¨B«Ð0°&Ð9Ð9ùó	 
2ùÒ4HùÛ	1ùÒ4Hs   †B2©B8ÁB=Á$Cc                 óè  • [        U 5      n[        XU5      u  pE0 n0 n0 n[        U5       H5  n	UR                  UR	                  U	5      / 5      R                  X	   5        M7     [        [        U5      5       H7  n
UR                  UR	                  X:-   5      / 5      R                  X   5        M9     UR                  5        H8  u  u  p›nUR                  UR	                  U	5      / 5      R                  U5        M:     / nUR                  5        H]  u  pï[        U5      [        ::  a  M  [        UR                  U/ 5      UR                  U/ 5      U5      nUc  ML  UR                  U5        M_     U$ )uó  Group mutually-intersecting lines into table clusters (union-find).

Returns a list of ``(bbox, joints)`` tuples â€” one per connected
component of crossing lines â€” where ``bbox`` is ``(x0, y0, x1, y1)``
(left, bottom, right, top in PDF coords) and ``joints`` is the list of
``(x, y)`` crossing points within that component. Clusters with
``<= _MIN_JOINTS`` crossings are dropped (noise / stray L-corners),
matching the raster pipeline's joint-count filter.

This is the vector-native replacement for the raster
``find_contours`` + ``find_joints`` pair: instead of one bbox over the
union of every line (the Stage-1 prototype's flaw, which merged
separate tables), it isolates each grid as its own component, so
multi-table pages yield multiple bboxes.
)
rm   ræ   rÄ   Ú
setdefaultrÍ   rU   ÚitemsÚ_MIN_JOINTSrí   Úget)rÞ   rß   r¥   rà   rá   râ   Úcomp_hÚcomp_vÚcomp_jointsrª   rã   Ú_virå   r§   Úrootrn   r«   s                    r   Ú_cluster_linesrø   o  sA  € ô  ÐÓ
€CÜ#Ð$4ÀcÓJ�M€Bà€FØ€FØ€KÜ�CŽjˆØ×Ñ˜"Ÿ'™' "›+ rÓ*×1Ñ1Ð2BÑ2FÖGñ ä”C˜Ó'Ö(ˆØ×Ñ˜"Ÿ'™' #¡(Ó+¨RÓ0×7Ñ7¸Ñ8JÖKñ )à"Ÿ™Ö*‰‰	ˆ�2Ø×Ñ˜rŸw™w r›{¨BÓ/×6Ñ6°rÖ:ñ +ð €HØ#×)Ñ)Ö+‰ˆÜˆv‹;œ+Ó%ÙÜ §
¡
¨4°Ó 4°f·j±jÀÀrÓ6JÈFÓSˆØÓØ�O‰O˜GÖ$ñ ,ð €Or   c                 óv   • [        XU5      nU VVs/ s H  u  pEUPM	     nnnUR                  S S9  U$ s  snnf )uC  Vector-native table-bbox detection (Stage 2 of #763).

Companion to the raster :func:`find_contours`. Given ruled lines in
PDF coords (from :func:`find_lines_from_layout`), returns a list of
``(x0, y0, x1, y1)`` table bounding boxes â€” one per connected cluster
of mutually-intersecting lines â€” so a page with two separate tables
yields two bboxes (the Stage-1 prototype merged them).

Returns
-------
list[tuple[float, float, float, float]]
    ``(x0, y0, x1, y1)`` = (left, bottom, right, top) in PDF coords,
    sorted top-to-bottom (descending y0) to match reading order.
c                 ó   • U S   * $ )Nr@   r²   )rÑ   s    r   Ú<lambda>Ú*find_contours_from_lines.<locals>.<lambda>§  s
   € ˜q ™t™er   )r[   )rø   r­   )rÞ   rß   r¥   r§   Úbboxr<   Úbboxess          r   Úfind_contours_from_linesrÿ   –  s?   € ô Ð.ÀÓD€HÙ"*Ô+¢(‘w�t‹d¡(€FÑ+Ø
‡K�K‘O€KÑ$Ø€Mùó ,s   ’5c                 óP   • [        XU5       VVs0 s H  u  p4X4_M	     snn$ s  snnf )uª  Vector-native joint detection (Stage 2 of #763).

Companion to the raster :func:`find_joints`. Returns a dict keyed by
table bbox ``(x0, y0, x1, y1)`` (left, bottom, right, top in PDF
coords) whose value is the list of ``(x, y)`` line intersections in
that table â€” the same shape the raster pipeline feeds into cell
construction, but computed exactly from vector geometry instead of
``np.multiply`` of two rasterised masks.
)rø   )rÞ   rß   r¥   rý   rn   s        r   Úfind_joints_from_linesr  «  s5   € ô +Ð+;ÈSÔQôâQ‰LˆDð 	ŠÙQòð ùó s   �"c                 ó8   • U c  g[        [        U 5      5      U:¬  $ )u»  Probe whether a layout carries enough vector ruled lines.

Used by ``flavor='lattice'``'s ``engine='auto'`` to decide between the
vector and raster line-detection paths (#763). Returns ``True`` when
the layout tree contains at least ``min_lines`` stroked ``LTLine`` /
``LTRect`` edges (via :func:`_ruled_lines_from_layout`).

A ``None`` layout (e.g. probed before page preparation) returns
``False`` â€” the caller should fall back to raster.
F)rm   r‘   )r‰   Ú	min_liness     r   Úlayout_has_ruled_linesr  Â  s"   € ð �~ØÜÔ'¨Ó/Ó0°IÑ=Ð=r   )Fé   éþÿÿÿÚ )Nr(   é(   r   r   )r   )r(   )&rÚ   r	   Únumpyr   Úplaya.minerr   r   r   r_   r   r%   r>   r*   r+   r,   r2   ri   rz   r–   Ú_LINE_COALESCE_TOLr‡   r}   r‘   rœ   r—   r¢   r¤   Ú
_JOINT_TOLrñ   r¿   rÁ   ræ   rí   rø   rÿ   r  Ú_VECTOR_ENGINE_MIN_LINESr  r²   r   r   Ú<module>r     s  ðÙ vã 
Û Ý #Ý Ý ð "Ð òð, GIô+ð` ØØØØô;&ò|ò8ô4òBò:%òP(ðn Ð ð Ð ð
 Ð ò1ò#ôL-7ð` 4Fô 3òlòð €
ð
 €ð (2ô ÷$!ñ !ò$ò(:ð :Dô $ðN DNô ð* BLô ð( Ð ð .Fõ >r   