
    IpjZ                        S r SSKrSSKJrJr   " S S5      rSS.S jrS	S.S
 jrS	S.S jrS r	S r
S rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS rS  rS! rS" rS# r S$ r!SS%S&.S' jr"g)(aF  
Copyright (C) 2023 Artifex Software, Inc.

This file is part of PyMuPDF.

PyMuPDF is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.

PyMuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.

You should have received a copy of the GNU Affero General Public License
along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>

Alternative licensing terms are available from the licensor.
For commercial licensing, see <https://www.artifex.com/> or contact
Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
CA 94129, USA, for further information.

---------------------------------------------------------------------

PyMuPDF table cell-span resolution (opt-in extension).

Provides SpanCell and resolve_spans (plus the _span_* helpers), which
reconstruct a detected table's merged-cell (colspan / rowspan) structure.
Re-exported by pymupdf.table; never runs on the default find_tables() path.
Reuses the word-selection helpers of pymupdf._table_refine.
    N)_refine_is_vertical_or_rotated_refine_page_wordsc                   "    \ rS rSrSrSS jrSrg)SpanCell*   a  One reconstructed table cell after span resolution (PyMuPDF extension).

``bbox`` is the placement's ``(x0, y0, x1, y1)`` union rect, ``text`` the
page text it claims (lines joined by ``\n``), and ``colspan``/``rowspan``
how many grid columns/rows it covers. resolve_spans always sets a real bbox;
a caller padding its own grid may construct SpanCells with ``bbox=None``.

``tag`` is the cell's HTML tag (``"td"``/``"th"``), defaulting to ``"td"``.
resolve_spans leaves it at the default; find_tables(refine=True) overwrites
it from the resolved header region so Table.to_html() can serialize the grid
directly, and a caller building its own grid may set it too.c                 @    Xl         X l        X0l        X@l        XPl        g N)bboxtextcolspanrowspantag)selfr
   r   r   r   r   s         P/var/www/html/pdf-tiff/venv/lib/python3.13/site-packages/pymupdf/_table_spans.py__init__SpanCell.__init__7   s    		    )r
   r   r   r   r   N)td)__name__
__module____qualname____firstlineno____doc__r   __static_attributes__ r   r   r   r   *   s    
Dr   r   g      @)	tolerancec                    / n[        U 5       H1  nU(       a  X2S   -
  U::  a  M  UR                  [        U5      5        M3     U$ )zGreedy 1-D clustering of edge coordinates into slot boundaries.

Keeps a sorted value only when it is more than ``tolerance`` from the last
kept boundary, so the first value of each run is the retained boundary.)sortedappendfloat)valuesr   
boundariesvalues       r   _span_clustered_boundariesr%   @   sE    
 J%R.0I=%,'   r         ?c                    Sn[        [        U5      S-
  5       H.  nX%   X%S-      -   S-  nX-
  Us=::  a
  X-   ::  d  M%  O  M)  US-  nM0     [        SU5      $ )zKHow many [boundaries[i], boundaries[i+1]] slots the span start..end covers.r             @)rangelenmax)startendr#   r   countindexmidpoints          r   _span_covered_slot_countr2   M   sd    Es:*+%
19(==D;CO;;QJE , q%=r   c                   / n[        [        U5      S-
  5       HP  nX   XS-      -   S-  nU R                  U-
  Us=::  a  U R                  U-   ::  d  M;  O  M?  UR	                  U5        MR     U(       d  g[        U5      [        U5      S-   4$ )zIThe half-open column-slot range (first, last+1) a rect's x-extent covers.r(   r)   )r   r(   )r*   r+   x0x1r    minr,   )rectx_boundariesr   hitsr0   r1   s         r   _span_slot_ranger:   W   s    Ds<(1,- ',qy*AASH77Y(Adgg	.AAAKK . Is4y1}%%r   c                 <    U S   US   :  =(       a    US   U S   :  $ Nr   r(   r   abs     r   _span_ranges_intersectr@   c   s%    Q4!A$;&1Q4!A$;&r   c                     [        UR                  5      U s=:*  =(       a    [        UR                  5      :*  Os  =(       a;    [        UR                  5      Us=:*  =(       a    [        UR                  5      :*  $ s  $ r	   )r!   r4   r5   y0y1)xyr7   s      r   _span_point_in_rectrF   g   sP    >Q00%.0ZU477^q5Z5ZERVRYRYN5ZZ5ZZr   c           	          [         R                  " [        S U  5       5      [        S U  5       5      [        S U  5       5      [        S U  5       5      5      $ )Nc              3   8   #    U  H  oR                   v   M     g 7fr	   )r4   .0r7   s     r   	<genexpr>#_span_rect_union.<locals>.<genexpr>m        &GG   c              3   8   #    U  H  oR                   v   M     g 7fr	   rB   rI   s     r   rK   rL   n   rM   rN   c              3   8   #    U  H  oR                   v   M     g 7fr	   )r5   rI   s     r   rK   rL   o   rM   rN   c              3   8   #    U  H  oR                   v   M     g 7fr	   rC   rI   s     r   rK   rL   p   rM   rN   )pymupdfRectr6   r,   )rectss    r   _span_rect_unionrW   k   sK    <<&&&&&&&&&&&&	 r   c                     [        U R                  UR                  5      [        U R                  UR                  5      -
  $ r	   )r6   r5   r,   r4   r=   s     r   _span_x_overlaprY   t   s)    qttQTT?Sqtt_,,r   c                 `    [        X5      nUS::  a  g[        SUR                  5      nX#-  S:  $ )a  True if a text span overlaps a cell by enough x to signal a merge.

A few points of overlap is noise (a span drifting across a column line with
trailing whitespace or a currency glyph); require >2pt and >=15% of the cell
width before treating it as a merged-cell signal.r)   Fr&   g333333?)rY   r,   width)	span_rect	cell_rectoverlap
cell_widths       r   _span_substantial_x_overlapr`   x   s6     i3G#~S)//*J4''r   c                     U (       d  / $ / nU S   =p#U SS  H$  nXCS-   :X  a  UnM  UR                  X#45        U=p#M&     UR                  X#45        U$ )z>Collapse a sorted index list into (start, end) inclusive runs.r   r(   N)r    )indicesrangesr-   r.   r0   s        r   _span_contiguous_rangesrd      sj    	F!*E!GCul#  MM5,Mr   c                     / n[        U S S9 HQ  u  p#nX#:X  a  M  U(       a  X!S   S   :  a  UR                  X#U/45        M5  US   u  pVnU[        Xc5      Xt/-   4US'   MS     U$ )zIMerge overlapping (start, end, text) intervals, accumulating their texts.c                     U S   U S   4$ r<   r   items    r   <lambda>'_span_merge_intervals.<locals>.<lambda>   s    Qa?Qr   keyr   r(   )r   r    r,   )	intervalsmergedr-   r.   r   
prev_startprev_endtextss           r   _span_merge_intervalsrr      s{    F"92QRD<A.MM5v./&,Rj#
e #h"4efnEr
 S Mr   c                 @   U R                  S/ 5       Vs/ s Hl  n[        UR                  S5      =(       d    S5      R                  5       (       d  M;  [        UR                  S5      =(       d    S5      R                  5       PMn     nnSR                  U5      $ s  snf )z=Space-joined non-empty span texts of a get_text('dict') line.spansr     )getstrstripjoin)linespanpartss      r   _span_line_textr~      s     HHWb))Dtxx%2&,,. 	,DHHV"#))+) 
 
 88E?s   :B5Bc                    U R                  S5      nU(       a)  [        R                  " U5      nUR                  (       d  U$ U R                  S/ 5       Vs/ s HI  nUR                  S5      (       d  M  [        R                  " UR                  S5      =(       d    / 5      PMK     nnU Vs/ s H  o"R                  (       a  M  UPM     nnU(       d  g[	        U5      $ s  snf s  snf )zKBounding rect of a dict line (its bbox, else the union of its span bboxes).r
   rt   N)rw   rT   rU   is_emptyrW   )r{   r
   r7   r|   rV   s        r   _span_line_rectr      s    88FD||D!}}K HHWb))D88F 	-TXXf%+,) 
 
 $9ed==TeE9E""
 :s   C42C,C$C$c                    [        U SS5      nUb  U$ / nU R                  S5      R                  S/ 5       H{  nUR                  S5      S;  a  M  UR                  S/ 5       HK  n[        U5      (       d  M  [	        U5      nU(       d  M)  [        U5      nUc  M9  UR                  Xe45        MM     M}      [        U SU5        U$ ! [         a     U$ f = f)zVertical/rotated text lines as (rect, text), cached on the page.

Selects non-horizontal lines whose reading order get_text('dict') already
preserves._span_vertical_lines_cacheNdictblockstypeNr   lines)	getattrget_textrw   r   r~   r   r    setattr	Exception)pagecachedr   blockr{   r   r7   s          r   _span_vertical_text_linesr      s    
 T7>FEv&**8R899VI-IIgr*D1$77"4(D"4(D|LL$& + 92E: L  Ls   6C 
CCc                   ^^ U(       d  g/ n[        U 5       H  u  pE[        UR                  5      [        UR                  5      -   S-  m[        UR                  5      [        UR
                  5      -   S-  m[        TTU5      (       d  Mu  UR                  XE45        M     U(       d  gSnU H:  u  nu  ppnX-   S-  mX-   S-  m[        UU4S jU 5       5      (       d  M5  US-  nM<     U[        S[        U5      5      -  S:  a  g[        S U 5       5      S:  a  gS	R                  S
 [        US S9 5       5      $ )a  Text of vertical lines centered in rect, when they dominate the rect.

Returns the stacked line text only if vertical lines are centered in the rect,
cover >=60% of the rect's selected words, and carry >=2 tokens; else None so
the caller falls back to horizontal line synthesis.N      ?r   c              3   B   >#    U  H  u  p[        TTU5      v   M     g 7fr	   )rF   )rJ   	line_rect_cxcys      r   rK   /_span_vertical_text_for_rect.<locals>.<genexpr>   s      U*,)"2r955*s   r(   g333333?c              3   X   #    U  H   u  p[        UR                  5       5      v   M"     g 7fr	   )r+   splitrJ   r   r   s      r   rK   r      s      
7J3tzz|Js   (*   
c              3   *   #    U  H	  u  pUv   M     g 7fr	   r   r   s      r   rK   r      s      
GA 	
   c                     [        U S   R                  5      [        U S   R                  U S   R                  -   S-  5      * 4$ )Nr   r   )r!   r4   rB   rC   rg   s    r   ri   ._span_vertical_text_for_rect.<locals>.<lambda>   s9    eDGJJ/%ad1gjj9PTW8W2X1XYr   rk   )r   r!   r4   r5   rB   rC   rF   r    anyr,   r+   sumrz   r   )r   r7   selected_words
candidatesr   r   vertical_hitsr   wx0wy0wx1wy1r   r   s               @@r   _span_vertical_text_for_rectr      s6    J4T:	ILL!E),,$773>ILL!E),,$773>r2t,,y/0	 ;
 M&4""Cci3i3U*UUUQM	 '5
 s1c.122S8

7J
77!;99 Y
  r   c                    U (       d  gU  VVVs/ s H  u  pp2[        SX1-
  5      PM     nnnn[        U5      [        U5      S-     n[        SUS-  5      n/ n[        U S S9 H  u  pp9X-   S-  n
SnUnU H(  n[        U
[	        US	   5      -
  5      nX::  d  M$  UnUnM*     Uc  UR                  XU	4/S
.5        MZ  US   R                  X45        [        US   5      n[	        US	   5      US-
  -  U
-   U-  US	'   M     / n[        US S9 H6  nUR                  SR                  S [        US   S S9 5       5      5        M8     SR                  U5      $ s  snnnf )zJoin center-point-selected cell words into text, re-synthesizing lines.

``words`` are (y0, x0, y1, text) tuples; they are grouped into lines by an
adaptive median-height nearest-line rule, each line's words ordered by x, and
the lines joined by newlines.ru   g?r   r)   g?c                     U S   U S   4$ r<   r   rg   s    r   ri   *_span_words_to_line_text.<locals>.<lambda>  s    DGT!W;Mr   rk   Ncenter_y)r   wordsr   r(   c                     [        U S   5      $ )Nr   )r!   rg   s    r   ri   r     s    uT*5E/Fr   rv   c              3   *   #    U  H	  u  pUv   M     g 7fr	   r   r   s      r   rK   +_span_words_to_line_text.<locals>.<genexpr>  s     "g7fGA47fr   c                     U S   $ r   r   rg   s    r   ri   r     s    ^bcd^er   r   )r,   r   r+   absr!   r    rz   )r   rB   r   rC   heightsmedian_heightline_thresholdr   r4   r   r   	best_linebest_distancer{   distancer/   
text_liness                    r   _span_words_to_line_textr      s{    5:;U\RBs3 UG;7OCLA$56Mmd23NE"5.MNg_	&D2d:&6 778H( 	 (	 
 LLbJ<@A'!!2*-Ig&'!&y'<!=!Kb!PTY Y	* O Ju"FG#(("gvd7mQe7f"ggh H99Z  - <s   Ec                 b    U u  pp4n[        U5      [        U5      [        U5      [        U5      4$ )zKReorder a (x0, y0, x1, y1, text) word to the (y0, x0, y1, text) line tuple.)r!   rx   )wordr4   rB   _x1rC   r   s         r   _span_word_line_tupler   !  s-     BCT"IuRy%)SY77r   c                     / n[        U 5       H^  u  p4Uu  pVpxn	[        U	5      R                  5       (       d  M+  XW-   S-  n
Xh-   S-  n[        XU5      (       d  ML  UR	                  X445        M`     U$ )z(index, word) pairs whose center lies in rect, index into ``page_words``.

The index is what lets resolve_spans claim each page word for exactly one
placement (an earlier cell's word is not re-claimed by a later one).r   )	enumeraterx   ry   rF   r    )
page_wordsr7   selectedr0   r   r   r   r   r   r   r   r   s               r   _span_select_words_in_rectr   '  st    
 H ,#' #D4y  i3i3rt,,OOUM* - Or   c           	          [        XU5      nUb  U$ [        U VVs/ s H  u  pE[        U5      PM     snn5      $ s  snnf )zIText for a rect: vertical-line text if it dominates, else line synthesis.)r   r   r   )r   r7   r   vertical_textr   r   s         r   _span_words_text_for_rectr   8  sB    0^LM #P^$_P^WQ%:4%@P^$_``$_s   ;
c                     [        X!5       VVs/ s H  u  pEXC;  d  M  XE4PM     nnnU H  u  pGUR                  U5        M     [        XU5      $ s  snnf )zKText of rect's words, skipping words already claimed and claiming the rest.)r   addr   )r   r7   r   claimed_wordsr0   r   r   r   s           r   _span_claim_text_in_rectr   @  sc     6jGGKE% 	G  
 %  $T::s
   AAc                 N   [        U SS5      nUb  U$ / nU R                  S5      R                  S/ 5       H  nUR                  S/ 5       H  nUR                  S/ 5       H  n[        UR                  S5      =(       d    S5      R	                  5       n[        U5      S	:  a  ME  UR                  S
5      nU(       d  M_  [        R                  " U5      nUR                  (       a  M  UR                  X45        M     M     M      [        U SU5        U$ ! [         a     U$ f = f)zPage text spans as (rect, text), length>=2, cached on the page.

These drive merged-cell detection: a single span whose x-extent crosses a
grid column line signals cells the line grid split but text joins._span_text_spans_cacheNr   r   r   rt   r   ru   r   r
   )r   r   rw   rx   ry   r+   rT   rU   r   r    r   r   )	r   r   rt   r   r{   r|   r   r
   r7   s	            r   _span_text_spansr   L  s   
 T3T:FEv&**8R8IIgr*D"-488F+1r288:t9q=xx'||D)==d\* . + 9.6 L  Ls   D 
D$#D$c           	         [        U 5      S:  a  / $ [        S U  5       5      n[        S U  5       5      n/ nU H  u  pVUR                  UR                  -   S-  nXrS-
  :  d  XsS-   :  a  M3  [        U 5       VV	s/ s H  u  p[        XY5      (       d  M  UPM     n
nn	[        U
5      S:  a  Mu  [        U
5       H  u  pX:  d  M  UR                  XU45        M!     M     [        U5      $ s  sn	nf )a>  Column ranges within one grid row that a single text span crosses.

``entries`` are the row's cell rects (in column order). A text span centered
in the row band and substantially overlapping >=2 adjacent cells marks those
cells for merging; overlapping intervals are merged. Returns
(start, end, texts) with end>start.r   c              3   8   #    U  H  oR                   v   M     g 7fr	   rP   rJ   entrys     r   rK   +_span_crossing_intervals.<locals>.<genexpr>r       /wewrN   c              3   8   #    U  H  oR                   v   M     g 7fr	   rS   r   s     r   rK   r   s  r   rN   r)   r&   )
r+   r6   r,   rB   rC   r   r`   rd   r    rr   )entries
text_spansrow_y0row_y1rm   r\   	span_textr   positionr   hit_positionsr-   r.   s                r   _span_crossing_intervalsr   i  s     7|a	/w//F/w//FI *	LL9<</36sl"h#&= $-W#5
#5*9< #5 	 

 }!1-@JE{  %i!89 A !+ !++
s   ;C,C,c                     X:  a  gU H7  nUS   S;  a  M  US   U ::  d	  US   U:  a  M"  [        XS   5      (       d  M7    g   g)NFrole>   headerheader_leafheader_grouprowcolsT)r@   )row_idxr   base
body_startothers        r   _span_is_leaf_header_ranger     sV    = II<7"eElj&@!$f66  r   c                    / n[        U 5       Hw  u  pE[        U Vs/ s H	  ofc  M  UPM     sn5       HN  u  pv[        R                  " U5      n[        X5      n	UR	                  UUUU	U	S   U	S   -
  XB:  a  SOSS.5        MP     My     U H-  n
U
S   S:w  a  M  [        U
S   U
S   X25      (       a  S	OS
U
S'   M/     U$ s  snf )zDescribe every grid cell by row/slot-range/role for the strict-colspan gate.

Header cells split into ``header_leaf`` (no header cell below within body) vs
``header_group`` (spans over a lower leaf), which the reject rules compare
against the body split.r(   r   bodyr   )r   r   r7   r   r   r   r   r   r   r   r   )r   rT   rU   r:   r    r   )cellsr8   r   r   r   r   cellr   r7   r   rh   s              r   _span_build_base_cellsr     s     D!%('#(R#$#(RSNH<<%D#D7DKK" (  #Awa0&-&;F	 T ) <8# *$u+tF|TVV  	V  K+ )Ss
   B>
B>
c           	        ^  [        U Vs1 s H  o3S   S:X  d  M  US   U:  d  M  US   iM     sn5      nU H  nU Vs/ s H1  nUS   S:X  d  M  US   U:X  d  M  [        T US   5      (       d  M/  UPM3     nnU(       d  MJ  [        U5      S:X  a  US   S   T :X  a  Mg  [        U 4S jU 5       5      n[	        U 4S jU 5       5      nUT S   ::  d  M  UT S   :  d  M    g	   g
s  snf s  snf )zDTrue if some body row splits the column range ``cols`` into >1 cell.r   r   r   r   r(   r   c              3   N   >#    U  H  n[        TS    US   S    5      v   M     g7fr   r   Nr,   rJ   rh   r   s     r   rK   ._span_body_rows_split_under.<locals>.<genexpr>  s'     KddCQfa99d   "%c              3   N   >#    U  H  n[        TS    US   S    5      v   M     g7fr(   r   Nr6   r   s     r   rK   r     s'     IDD#d1gtF|A77Dr   TF)r   r@   r+   r6   r,   )	r   r   r   rh   	body_rowsr   r9   covered_startcovered_ends	   `        r   _span_body_rows_split_underr     s   mV8NSWX]S^blSlUmnI 
F|v% *.u+*@ E[\`bfgmbnEo  	 

 t9>d1gfo5KdKKIDIIDG#tAw(>   n
s&   C$C$	C$C)C)C)1C)c                 <  ^  U Vs/ s H%  o"S   S:X  d  M  [        T US   5      (       d  M#  UPM'     nnU(       d  g[        U5      S:X  a  US   S   T :X  a  g[        U 4S jU 5       5      n[        U 4S jU 5       5      nUT S   :*  =(       a    UT S   :  $ s  snf )	zGTrue if leaf-header cells split the column range ``cols`` into >1 cell.r   r   r   Fr(   r   c              3   N   >#    U  H  n[        TS    US   S    5      v   M     g7fr   r   r   s     r   rK   0_span_header_leafs_split_over.<locals>.<genexpr>  s'     G$$DGT&\!_55$r   c              3   N   >#    U  H  n[        TS    US   S    5      v   M     g7fr   r   r   s     r   rK   r    s'     Ec$q'4<?33r   )r@   r+   r6   r,   )r   r   rh   r9   r   r   s   `     r   _span_header_leafs_split_overr    s    !rTT&\]%BDG]^bdhiodpGqDTDr
4yA~$q'&/T1G$GGMEEEKDG#>tAw(>> ss   BBBc                     US   US   -
  S::  a  gX:  a%  [        XX#5      (       d  g[        XU5      (       a  gg[        X5      (       a  gg)zJWhether to reject a candidate merge over ``cols`` as a (reason,) mismatch.r(   r   )Fru   )T.header_leaf_colspan_changed_against_body_split)T.body_colspan_changed_against_header_leaf_split)r   r   r  r   r   r   r   s       r   #_span_reject_colspan_mismatch_merger    sT    AwaA)'JJ&t:>>I$T00Er   c                 r    / nXUS-     H)  n[        XF5      nUR                  [        XU5      5        M+     U$ )Nr(   )r   r    r   )r   r   r-   r.   r   rq   r   r   s           r   _span_cell_texts_for_entriesr
    s?    Eq)*:=.tEBC * Lr   c                     U S:w  a  g[        XX4U5       Vs/ s H  ofR                  5       PM     nn[        U5      (       + $ s  snf )zAllow an otherwise-rejected header merge when a covered part is empty.

A header leaf that would be rejected against a body split is still merged if
one of the merged parts has no text (an empty header slot the body fills).r  F)r
  ry   all)reasonr   r   r-   r.   r   r   
part_textss           r   0_span_allow_header_colspan_merge_with_empty_partr    sH    
 AA+GW\cm+no+n4**,+nJo: ps   AF)header_row_countstrict_colspanc                   [        U5      n/ n/ nU Hu  nU Hl  nUc  M  [        R                  " U5      n	UR                  U	R                  U	R
                  /5        UR                  U	R                  U	R                  /5        Mn     Mw     [        U5      n
[        U5      nU(       a(  [        S[        [        U=(       d    S5      U5      5      OSnU(       a  [        XU5      O/ n[        U 5      n[        U 5      n[        5       n/ n[!        U5       GH  u  nn/ nU Vs/ s H  oc  M  [        R                  " U5      PM     nn[#        UU5      nU VVVs0 s H  u  nnnUUU4_M     nnnnSnU[        U5      :  Ga%  UR%                  U5      nUbq  Uu  nn['        UUUS-    5      n	U(       aN  [)        X5      n[+        UUUUS9u  nn U(       a  [-        U U UUUUS9(       a  SnU(       a  UU   n	US-  nOUS-   nOUS-   nO
UU   n	US-  nU	R.                  (       a  M  [1        U	R                  U	R
                  U
5      n![1        U	R                  U	R                  U5      n"UR3                  [5        [7        U	5      [9        X	UU5      U!U"S95        U[        U5      :  a  GM%  UR3                  U5        GM     U$ s  snf s  snnnf )am  Resolve a detected table's merged-cell (colspan/rowspan) structure.

``cells`` is a row-major grid: a list of rows, each a list of ``[x0, y0, x1,
y1]`` cell rectangles (``None`` for a gap) -- the same grid shape
:func:`refine_grid` accepts. Returns a row-major grid of :class:`SpanCell`
placements, ragged where cells span: each placement carries its union
``bbox``, the page ``text`` it claims, and its ``colspan``/``rowspan`` (how
many clustered column/row slots it covers). Every page word is claimed by at
most one placement.

``header_row_count`` is the number of leading header rows (default a
conservative 1); it only matters together with ``strict_colspan``.
``strict_colspan`` (default False), when set, refuses a merge whose colspan
would contradict the header/body column split. This is a PyMuPDF extension;
it reads page text/graphics but does not mutate the page.
r(   r   r  )r  r   r   r-   r.   r   F)r
   r   r   r   )r+   rT   rU   extendr4   r5   rB   rC   r%   r,   r6   intr   r   r   setr   r   rw   rW   r:   r  r  r   r2   r    r   tupler   )#r   r   r  r  rowsx_edgesy_edgesr   r   r7   r8   y_boundariesr   
base_cellsr   r   r   
placementsr   placement_rowr   rm   r-   r.   rq   intervals_by_startr   intervalend_position_crossing_textscandidate_colsrejectr  r   r   s#                                      r   resolve_spansr$    s   " u:DGGD|<<%DNNDGGTWW-.NNDGGTWW-.   .g6L-g6LBFQC 0 5A6=>AJLZ'ZH`bJ!$'J#D)JEMJ!%(25J#$%7<<%#J,WjA	KTU96GeS%ec5\19UW%)--h7H#08-o'<!;K(LM!%5d%IN%H '+'#-	&NFF "R%! '&(#-# "'&x0 A#/!#3+a/Hx(A}}.twwNG.twwNG  t1$j-X##	K W%Z 	-(g )j g KUs   !J?+J?K
)#r   rT   pymupdf._table_refiner   r   r   r%   r2   r:   r@   rF   rW   rY   r`   rd   rr   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r
  r  r$  r   r   r   <module>r&     s   B  , 58 
 CF  7: 	&'[-
( #$: F!B8"a	;:,B
@(	? 48 [r   