
    4vjZ                         d Z ddlZddlmZmZ  G d d      ZdddZd	dd
Zd	ddZd Z	d Z
d Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd  Zd! Zd" Zd# Z d$ Z!dd%d&d'Z"y)(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                       e Zd ZdZddZy)SpanCella  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                 J    || _         || _        || _        || _        || _        y N)bboxtextcolspanrowspantag)selfr	   r
   r   r   r   s         G/root/.hermes/venv/lib/python3.12/site-packages/pymupdf/_table_spans.py__init__zSpanCell.__init__7   s%    		    N)td)__name__
__module____qualname____doc__r    r   r   r   r   *   s    
Dr   r   g      @)	tolerancec                z    g }t        |       D ]*  }|r||d   z
  |k  r|j                  t        |             , |S )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!   @   sL    
 J (%*R.0I=%,'( r         ?c                    d}t        t        |      dz
        D ]-  }||   ||dz      z   dz  }| |z
  |cxk  r	||z   k  s&n )|dz  }/ t        d|      S )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_countr.   M   sn    Es:*+ u%
519(==D9;C)O;QJE q%=r   c                   g }t        t        |      dz
        D ]M  }||   ||dz      z   dz  }| j                  |z
  |cxk  r| j                  |z   k  s:n =|j	                  |       O |syt        |      t        |      dz   fS )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   hitsr,   r-   s         r   _span_slot_ranger6   W   s    Ds<(1,-  ',uqy*AASH77Y(Adgg	.AAKK Is4y1}%%r   c                 2    | d   |d   k  xr |d   | d   k  S Nr   r$   r   abs     r   _span_ranges_intersectr<   c   s%    Q4!A$;&1Q4!A$;&r   c                     t        |j                        | cxk  xr t        |j                        k  nc xr6 t        |j                        |cxk  xr t        |j                        k  S c S r   )r   r0   r1   y0y1)xyr3   s      r   _span_point_in_rectrB   g   sJ    >Q0%.0ZU477^q5ZERVRYRYN5ZZ5ZZr   c           	          t        j                  t        d | D              t        d | D              t        d | D              t        d | D                    S )Nc              3   4   K   | ]  }|j                     y wr   )r0   .0r3   s     r   	<genexpr>z#_span_rect_union.<locals>.<genexpr>m        &DGG&   c              3   4   K   | ]  }|j                     y wr   r>   rE   s     r   rG   z#_span_rect_union.<locals>.<genexpr>n   rH   rI   c              3   4   K   | ]  }|j                     y wr   )r1   rE   s     r   rG   z#_span_rect_union.<locals>.<genexpr>o   rH   rI   c              3   4   K   | ]  }|j                     y wr   r?   rE   s     r   rG   z#_span_rect_union.<locals>.<genexpr>p   rH   rI   )pymupdfRectr2   r(   )rectss    r   _span_rect_unionrR   k   sK    <<&&&&&&&&&&&&	 r   c                     t        | j                  |j                        t        | j                  |j                        z
  S r   )r2   r1   r(   r0   r9   s     r   _span_x_overlaprT   t   s)    qttQTT?Sqtt_,,r   c                 b    t        | |      }|dk  ryt        d|j                        }||z  dk\  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?)rT   r(   width)	span_rect	cell_rectoverlap
cell_widths       r   _span_substantial_x_overlapr[   x   s:     i3G#~S)//*JZ4''r   c                     | sg S g }| d   x}}| dd D ]$  }||dz   k(  r|}|j                  ||f       |x}}& |j                  ||f       |S )z>Collapse a sorted index list into (start, end) inclusive runs.r   r$   N)r   )indicesrangesr)   r*   r,   s        r   _span_contiguous_rangesr_      sw    	F!*EC C!GCucl# MM5#,Mr   c                     g }t        | d       D ]N  \  }}}||k(  r|r||d   d   kD  r|j                  |||gf       0|d   \  }}}|t        ||      ||gz   f|d<   P |S )zIMerge overlapping (start, end, text) intervals, accumulating their texts.c                     | d   | d   fS r8   r   items    r   <lambda>z'_span_merge_intervals.<locals>.<lambda>   s    Qa?Q r   keyr   r$   )r   r   r(   )	intervalsmergedr)   r*   r
   
prev_startprev_endtextss           r   _span_merge_intervalsrl      s    F"92QR FsDC<A.MM5#v./&,Rj#
He #h"4etfnEr
F Mr   c                    | j                  dg       D cg c]Z  }t        |j                  d      xs d      j                         r,t        |j                  d      xs d      j                         \ }}dj                  |      S c c}w )z=Space-joined non-empty span texts of a get_text('dict') line.spansr
     )getstrstripjoin)linespanpartss      r   _span_line_textrx      s|     HHWb)txx%2&,,. 	DHHV"#))+E 
 88E?s   ABc                    | j                  d      }|r#t        j                  |      }|j                  s|S | j                  dg       D cg c];  }|j                  d      r(t        j                  |j                  d      xs g       = }}|D cg c]  }|j                  r| }}|syt	        |      S c c}w c c}w )zKBounding rect of a dict line (its bbox, else the union of its span bboxes).r	   rn   N)rq   rO   rP   is_emptyrR   )ru   r	   r3   rv   rQ   s        r   _span_line_rectr{      s    88FD||D!}}K HHWb)88F 	TXXf%+,E 
 $9d4==T9E9E""
 :s   A B7B<#B<c                    t        | dd      }||S g }| j                  d      j                  dg       D ]h  }|j                  d      dvr|j                  dg       D ]=  }t        |      st	        |      }|st        |      }|+|j                  ||f       ? j 	 t        | d|       |S # t        $ r Y |S w xY w)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_textrq   r   rx   r{   r   setattr	Exception)pagecachedr   blockru   r
   r3   s          r   _span_vertical_text_linesr      s    
 T7>FEv&**8R8 '99VI-IIgr* 		'D1$7"4(D"4(D|LL$&		''2E: L  Ls   !B0 0	B=<B=c                 \   |syg }t        |       D ]  \  }}t        |j                        t        |j                        z   dz  t        |j                        t        |j
                        z   dz  t        |      sp|j                  ||f        |syd}|D ]6  \  }\  }}	}
}}||
z   dz  |	|z   dz  t        fd|D              s2|dz  }8 |t        dt        |            z  dk  ryt        d |D              dk  ryd	j                  d
 t        |d       D              S )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   >   K   | ]  \  }}t        |        y wr   )rB   )rF   	line_rect_cxcys      r   rG   z/_span_vertical_text_for_rect.<locals>.<genexpr>   s     U,)Q"2r95Us   r$   g333333?c              3   T   K   | ]   \  }}t        |j                                " y wr   )r'   splitrF   r   r
   s      r   rG   z/_span_vertical_text_for_rect.<locals>.<genexpr>   s     
7D3tzz|
7s   &(   
c              3   &   K   | ]	  \  }}|  y wr   r   r   s      r   rG   z/_span_vertical_text_for_rect.<locals>.<genexpr>   s      At 	   c                     t        | d   j                        t        | d   j                  | d   j                  z   dz         fS )Nr   r   )r   r0   r>   r?   rb   s    r   rd   z._span_vertical_text_for_rect.<locals>.<lambda>   s;    eDGJJ/%ad1gjj9PTW8W2X1XY r   re   )r   r   r0   r1   r>   r?   rB   r   anyr(   r'   sumrt   r   )r   r3   selected_words
candidatesr   r
   vertical_hitsr   wx0wy0wx1wy1r   r   s               @@r   _span_vertical_text_for_rectr      sF    J4T: 1	4ILL!E),,$773>ILL!E),,$773>r2t,y$/0	1
 M&4 ""Cc3Ci3Ci3U*UUQM	
 s1c.122S8

7J
77!;99 Y
  r   c           
         | sy| D cg c]  \  }}}}t        d||z
         }}}}t        |      t        |      dz     }t        d|dz        }g }t        | d       D ]  \  }}}}	||z   dz  }
d}|}|D ]&  }t        |
t	        |d	         z
        }||k  s#|}|}( ||j                  |
||	fgd
       Y|d   j                  ||	f       t        |d         }t	        |d	         |dz
  z  |
z   |z  |d	<    g }t        |d       D ]8  }|j                  dj                  d t        |d   d       D                     : dj                  |      S c c}}}w )a  Join 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.ro   g?r   r%   g?c                     | d   | d   fS r8   r   rb   s    r   rd   z*_span_words_to_line_text.<locals>.<lambda>  s    DGT!W;M r   re   Ncenter_y)r   wordsr   r$   c                     t        | d         S )Nr   )r   rb   s    r   rd   z*_span_words_to_line_text.<locals>.<lambda>  s    uT*5E/F r   rp   c              3   &   K   | ]	  \  }}|  y wr   r   r   s      r   rG   z+_span_words_to_line_text.<locals>.<genexpr>  s     "gGAt4"gr   c                     | d   S r   r   rb   s    r   rd   z*_span_words_to_line_text.<locals>.<lambda>  s    ^bcd^e r   r   )r(   r   r'   absr   r   rt   )r   r>   r   r?   heightsmedian_heightline_thresholdr   r0   r
   r   	best_linebest_distanceru   distancer+   
text_liness                    r   _span_words_to_line_textr      s    5:;;\RBs3R ;G;7OCLA$56Mmd23NE"5.MN ZBD2g_	& 	)D2d:&6 778H=( 	 (		)
 LLbRJ<@A'!!2t*-Ig&'!&y'<!=!Kb!PTY Y	*Z Ju"FG i#(("gvd7mQe7f"gghi99Z  - <s   Ec                 f    | \  }}}}}t        |      t        |      t        |      t        |      fS )zKReorder a (x0, y0, x1, y1, text) word to the (y0, x0, y1, text) line tuple.)r   rr   )wordr0   r>   _x1r?   r
   s         r   _span_word_line_tupler   !  s1     BCT"IuRy%)SY77r   c                     g }t        |       D ]X  \  }}|\  }}}}}	t        |	      j                         s(||z   dz  }
||z   dz  }t        |
||      sF|j	                  ||f       Z |S )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   )	enumeraterr   rs   rB   r   )
page_wordsr3   selectedr,   r   r   r   r   r   r
   r   r   s               r   _span_select_words_in_rectr   '  s    
 H , +t#' S#sD4y Ci3Ci3r2t,OOUDM*+ Or   c           	      ~    t        | ||      }||S t        |D cg c]  \  }}t        |       c}}      S c c}}w )zIText for a rect: vertical-line text if it dominates, else line synthesis.)r   r   r   )r   r3   r   vertical_textr   r   s         r   _span_words_text_for_rectr   8  s@    0t^LM #P^$_WQ%:4%@$_``$_s   9
c                     t        ||      D cg c]  \  }}||vr||f }}}|D ]  \  }}|j                  |        t        | ||      S c c}}w )zKText of rect's words, skipping words already claimed and claiming the rest.)r   addr   )r   r3   r   claimed_wordsr,   r   r   r   s           r   _span_claim_text_in_rectr   @  so     6j$GE4% 
H 
  !q% !$T4::s   Ac                    t        | dd      }||S g }| j                  d      j                  dg       D ]  }|j                  dg       D ]  }|j                  dg       D ]  }t        |j                  d      xs d      j	                         }t        |      d	k  r>|j                  d
      }|sRt        j                  |      }|j                  rt|j                  ||f          	 t        | d|       |S # t        $ r Y |S w xY w)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   rn   r
   ro   r   r	   )r   r   rq   rr   rs   r'   rO   rP   rz   r   r   r   )	r   r   rn   r   ru   rv   r
   r	   r3   s	            r   _span_text_spansr   L  s   
 T3T:FEv&**8R8 +IIgr* 	+D"- 
+488F+1r288:t9q=xx'||D)==dD\*
+	++.6 L  Ls   -C< <	D	D	c           	         t        |       dk  rg S t        d | D              }t        d | D              }g }|D ]  \  }}|j                  |j                  z   dz  }||dz
  k  s||dz   kD  r3t        |       D 	cg c]  \  }}	t        ||	      r| }
}}	t        |
      dk  rkt        |
      D ]  \  }}||kD  s|j                  |||f       !  t        |      S c c}	}w )aN  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   4   K   | ]  }|j                     y wr   rK   rF   entrys     r   rG   z+_span_crossing_intervals.<locals>.<genexpr>r       /e/rI   c              3   4   K   | ]  }|j                     y wr   rN   r   s     r   rG   z+_span_crossing_intervals.<locals>.<genexpr>s  r   rI   r%   r"   )
r'   r2   r(   r>   r?   r   r[   r_   r   rl   )entries
text_spansrow_y0row_y1rg   rW   	span_textr   positionr   hit_positionsr)   r*   s                r   _span_crossing_intervalsr   i  s	    7|a	/w//F/w//FI * :	9LL9<</36fsl"h#&= $-W#5
%*9e< 
 

 }!1-@ 	:JE3U{  %i!89	:: !++
s   :Cc                 r    | |k\  ry|D ],  }|d   dvr|d   | k  s|d   |k\  rt        ||d         s, y y)NFrole>   headerheader_leafheader_grouprowcolsT)r<   )row_idxr   base
body_startothers        r   _span_is_leaf_header_ranger     s\    * = II<7"eElj&@!$f6 r   c                 j   g }t        |       D ]r  \  }}t        |D cg c]  }||	 c}      D ]N  \  }}t        j                  |      }t        ||      }	|j	                  ||||	|	d   |	d   z
  ||k\  rdndd       P t |D ]&  }
|
d   dk7  rt        |
d   |
d   ||      rd	nd
|
d<   ( |S c c}w )a
  Describe 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   r3   r   r   r   r   r   r   r   r   )r   rO   rP   r6   r   r   )cellsr4   r   r   r   r   cellr   r3   r   rc   s              r   _span_build_base_cellsr     s     D!%( '#(R$AQ(RS 	NHd<<%D#D,7DKK" (  #Awa0&-&;F		  
<8# *$u+tF|T:V  	V
 K+ )Ss
   B0
B0
c           	          t        |D ch c]  }|d   dk(  s|d   |k\  s|d    c}      }|D ]  }|D cg c]#  }|d   dk(  r|d   |k(  rt         |d         r|% }}|s4t        |      dk(  r|d   d    k(  rNt         fd|D              }t	         fd|D              }| d   k  s| d   k\  s y	 y
c c}w c c}w )zDTrue if some body row splits the column range ``cols`` into >1 cell.r   r   r   r   r$   r   c              3   H   K   | ]  }t        d    |d   d            ywr   r   Nr(   rF   rc   r   s     r   rG   z._span_body_rows_split_under.<locals>.<genexpr>  s$     KdCQfa9K   "c              3   H   K   | ]  }t        d    |d   d            ywr$   r   Nr2   r   s     r   rG   z._span_body_rows_split_under.<locals>.<genexpr>  s$     ID#d1gtF|A7Ir   TF)r   r<   r'   r2   r(   )	r   r   r   rc   	body_rowsr   r5   covered_startcovered_ends	   `        r   _span_body_rows_split_underr     s    mV8NSWX]S^blSlUmnI  
F|v%$u+*@E[\`bfgmbnEo 
 

 t9>d1gfo5KdKKIDIIDG#tAw(>  n
s   B>B>B>(Cc                     |D cg c]  }|d   dk(  st         |d         s| }}|syt        |      dk(  r|d   d    k(  ryt         fd|D              }t         fd|D              }| d   k  xr | d   k\  S c c}w )	zGTrue if leaf-header cells split the column range ``cols`` into >1 cell.r   r   r   Fr$   r   c              3   H   K   | ]  }t        d    |d   d            ywr   r   r   s     r   rG   z0_span_header_leafs_split_over.<locals>.<genexpr>  s$     G$DGT&\!_5Gr   c              3   H   K   | ]  }t        d    |d   d            ywr   r   r   s     r   rG   z0_span_header_leafs_split_over.<locals>.<genexpr>  s$     Ec$q'4<?3Er   )r<   r'   r2   r(   )r   r   rc   r5   r   r   s   `     r   _span_header_leafs_split_overr     s    !rTT&\]%BG]^bdhiodpGqDrDr
4yA~$q'&/T1G$GGMEEEKDG#>tAw(>> ss   BBBc                     |d   |d   z
  dk  ry| |k  rt        | |||      syt        |||      ryyt        ||      ryy)zJWhether to reject a candidate merge over ``cols`` as a (reason,) mismatch.r$   r   )Fro   )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     sU    AwaA)'4zJ&tT:>I$T40Er   c                 t    g }|||dz    D ]*  }t        ||      }|j                  t        | ||             , |S )Nr$   )r   r   r   )r   r   r)   r*   r   rk   r   r   s           r   _span_cell_texts_for_entriesr     sL    Eq) D*:u=.tUEBCD Lr   c                     | dk7  ryt        |||||      D cg c]  }|j                          }}t        |       S c c}w )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   rs   all)reasonr   r   r)   r*   r   r
   
part_textss           r   0_span_allow_header_colspan_merge_with_empty_partr    sL    
 AA+GgW\^acm+no4$**,oJo: ps   =F)header_row_countstrict_colspanc                2   t        |      }g }g }|D ]o  }|D ]h  }|t        j                  |      }	|j                  |	j                  |	j
                  g       |j                  |	j                  |	j                  g       j q t        |      }
t        |      }|r#t        dt        t        |xs d      |            nd}|rt        ||
|      ng }t        |       }t        |       }t               }g }t!        |      D ]  \  }}g }|D cg c]  }|t        j                  |       }}t#        ||      }|D ci c]  \  }}}|||f }}}}d}|t        |      k  r|j%                  |      }|d|\  }}t'        |||dz          }	|rFt)        |	|
      }t+        ||||      \  }} |rt-        | | ||||      rd}|r||   }	|dz  }n|dz   }n|dz   }n
||   }	|dz  }|	j.                  rt1        |	j                  |	j
                  |
      }!t1        |	j                  |	j                  |      }"|j3                  t5        t7        |	      t9        | |	||      |!|"             |t        |      k  r|j3                  |        |S c c}w c c}}}w )a  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'   rO   rP   extendr0   r1   r>   r?   r!   r(   r2   intr   r   r   setr   r   rq   rR   r6   r   r  rz   r.   r   r   tupler   )#r   r   r  r  rowsx_edgesy_edgesr   r   r3   r4   y_boundariesr   
base_cellsr   r   r   
placementsr   placement_rowr   rg   r)   r*   rk   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!%( 3)25J$9I7<<%JJ,WjA	KTUU6GeS%ec5\1UUW%)--h7H#08-o'<!;K(LM!%5dL%IN%H '+'#-	&NFF "R%! '&(#-# "'&x0 A#/!#3+a/Hx(A}}.twwNG.twwNG  t1$j-X##	K W%Z 	-(g3)j g KUs   JJ?J
)#r   rO   pymupdf._table_refiner   r   r   r!   r.   r6   r<   rB   rR   rT   r[   r_   rl   rx   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   