ó
    À‘´jÌ#  ã                  óì   • S r SSKJr  SSKJrJrJrJr  \\\\\4   r	\\\\\\
4   rSrSr\
R                  SSSSSSSSSSS.
5      rSS	 jrSS
 jrSS jrSS jrSS jrSS jr          SS jrg)ur  Tighten Camelot cell rectangles onto the words each cell prints.

Camelot reports each cell as structural grid geometry: the column's x-extent
crossed with the row band. On dense filings that geometry hugs the print, but
on sparse layouts the separators land mid-whitespace, so a cell's rectangle
runs far wider than its value and can sit half a line off the glyphs â€” a
highlight drawn from it cuts through the number instead of surrounding it.

The PDF itself records the rectangle of every word it prints (the text
layer). A cell whose text matches words near its structural rectangle takes
the extent of those words as its box (`tighten_box`). That box can reach past
the rectangle, because the words are gathered from a margin around it: a row
band can sit off its glyph line, and a glued `$` can print in the next
column.

A cell whose text matches no nearby words is clamped to the words whose
centers sit inside its rectangle (`ink_extent`). The clamp only shrinks the
rectangle. A cell with no box or no text keeps the structural rectangle, and
so does an unmatched cell with no word inside it, such as a cell of an
image-based table with no text layer. No box is ever invented.

Words arrive as `quber.agents.completeness.page_words` returns them:
(x0, y0, x1, y1, text) in PDF points, top-left origin. They are read from the
same text layer Camelot parses, through PyMuPDF, whose word splits can differ
from Camelot's. `tighten_box` works in that frame. `tighten_cell_boxes`
accepts and returns Camelot's frame â€” PDF points, bottom-left origin â€” so the
acquisition layer stores exactly what Camelot's contract promises, only
tighter.
é    )Úannotations)ÚListÚOptionalÚSequenceÚTupleg      @Ú'Ú"Ú-)
u   â€˜u   â€™u   â€œu   â€�u   â€�u   â€‘u   â€’u   â€“u   â€”u   âˆ’c                óf   • SR                  U R                  5       5      R                  [        5      $ )zDComparison form: whitespace removed, typographic punctuation folded.Ú )ÚjoinÚsplitÚ	translateÚPUNCT_VARIANTS)Útexts    ÚC/home/mande/repo/quber/src/quber/core/extractors/camelot/tighten.pyÚsqueezer   7   s"   € à�7‰7�4—:‘:“<Ó ×*Ñ*¬>Ó:Ð:ó    c                óŽ   • [        S U  5       5      [        S U  5       5      [        S U  5       5      [        S U  5       5      4$ )Nc              3  ó*   #   • U  H	  oS    v •  M     g7f©r   N© ©Ú.0Úws     r   Ú	<genexpr>Ú_union.<locals>.<genexpr>>   ó   é € Ðš#�QˆaŽDš#ùó   ‚c              3  ó*   #   • U  H	  oS    v •  M     g7f©é   Nr   r   s     r   r   r   ?   r   r   c              3  ó*   #   • U  H	  oS    v •  M     g7f©é   Nr   r   s     r   r   r   @   r   r   c              3  ó*   #   • U  H	  oS    v •  M     g7f©é   Nr   r   s     r   r   r   A   r   r   ©ÚminÚmax)Úruns    r   Ú_unionr-   <   sD   € äÑ™#ÓÓÜÑ™#ÓÓÜÑ™#ÓÓÜÑ™#ÓÓð	ð r   c                ó>  • U u  p#pEXS-
  n[        [        U5      nS U 5        VV	s/ s HW  u  p‰U	(       d  M  X'-
  US   US   -   S-  s=::  a
  XG-   ::  d  M-  O  M1  X6-
  US   US   -   S-  s=::  a
  XV-   ::  d  MP  O  MT  X‰4PMY     n
nn	U
R                  S S9  U
$ s  sn	nf )a  Words near the structural box, in reading order, with comparison text.

The margin is one box height vertically (camelot's row band drifts off
the glyph line) and one box height horizontally, never less than `PAD_X`
(a glued '$' can print in the neighboring column's band).
c              3  ó@   #   • U  H  o[        US    5      4v •  M     g7f)é   N)r   r   s     r   r   Ú_candidates.<locals>.<genexpr>Q   s   é € Ð7²¨Aœ  1¡›Õ'²ùs   ‚r   r%   r"   r(   c                óZ   • [        U S   S   U S   S   -   S-  [        -  5      U S   S   4$ )Nr   r"   r(   r%   )ÚroundÚ
LINE_QUANT)Úwts    r   Ú<lambda>Ú_candidates.<locals>.<lambda>V   s7   € œe R¨¡U¨1¡X°°1±°a±Ñ%8¸AÑ$=Ä
Ñ$JÓKÈRÐPQÉUÐSTÉXÑVr   ©Úkey)r+   ÚPAD_XÚsort)ÚboxÚwordsÚx1Úy1Úx2Úy2ÚbandÚmargin_xr   ÚtÚcands              r   Ú_candidatesrF   E   sÒ   € ð �N€BˆBØ‰7€DÜ”5˜$Ó€Hñ 8±Ô7ôâ7‰DˆAÜó 	ð ‰M˜a ™d Q q¡T™k¨QÑ.Õ?°"±-Ñ?ó 	ñ @ñ 	ð ‰I˜!˜A™$  1¡™+¨Ñ*Õ7¨b©iÑ7ó	 	ñ 8ñ	 	ˆ‹Ù7ð 	ñ ð 	‡I�IÑV€IÑWØ€Kùós"   ¦B·BÁBÁBÁ9BÁ=Bc                óô   • / n[        [        U 5      5       H]  nSn[        U[        U 5      5       H?  nX@U   S   -  n[        U5      [        U5      :¼  d  M'  XA:X  a  UR                  X545          M[     M_     U$ )zWEvery contiguous candidate run whose text equals `token`,
as (first, last) index pairs.r   r"   )ÚrangeÚlenÚappend)rE   ÚtokenÚoutÚiÚjoinedÚjs         r   Ú_token_runsrP   Z   ss   € ð "$€CÜ”3�t“9ÖˆØˆÜ�qœ#˜d›)Ö$ˆAØ˜1‘g˜a‘jÑ ˆFÜ�6‹{œc %›jÕ(Ø“?Ø—J‘J ˜vÔ&Úó %ñ ð €Jr   c           	     ó~  ^^^• [        U5      nU(       d  g[        X5      m/ n[        [        T5      5       Hu  nSn[        U[        T5      5       HW  nUTU   S   -  n[        U5      [        U5      :¼  d  M(  Xc:X  a)  UR	                  TXWS-     Vs/ s H  oˆS   PM	     sn5          Ms     Mw     U(       a/  U u  pšp¼X›-   S-  X¬-   S-  smmS
UU4S jjn[        [        XMS95      $ UR                  5        Vs/ s H  n[        U5      PM     nnU Vs/ s H  oî(       d  M  UPM     nn[        U5      S:  a  gU u  nnnnUU-   S-  UU-   S-  smm/ nU HK  n[        TU5      nU(       d    gSUUU4S jjn[        UUS9u  pWUR                  S	 TXWS-     5       5        MM     [        U5      $ s  snf s  snf s  snf )aÆ  The printed extent of `text` near the structural `box`, or None.

Two passes over the nearby words. The first looks for the whole cell text
as one contiguous run in reading order, which is the normal case. Of
several matching runs, the one nearest the box center wins. Failing that,
and when the text has at least two whitespace-separated tokens, each token
is matched as its own contiguous run and takes the run nearest the box
center, in any order. That grounds glued cells whose fragments print apart
('$' left-aligned, its value right-aligned) and labels whose superscript
markers sort off their line. The result is the union of the matched words,
so it can extend past `box`. No pass matching means None.
Nr   r"   r   r%   c                óÆ   >• [        S U  5       5      [        S U  5       5      -   S-  n[        S U  5       5      [        S U  5       5      -   S-  nUT-
  S-  UT-
  S-  -   $ )Nc              3  ó*   #   • U  H	  oS    v •  M     g7fr   r   r   s     r   r   Ú.tighten_box.<locals>.offset.<locals>.<genexpr>‰   ó   é € Ð(¢C˜q˜–d¢Cùr   c              3  ó*   #   • U  H	  oS    v •  M     g7fr$   r   r   s     r   r   rT   ‰   ó   é € Ð.AºS¸°®tºSùr   r%   c              3  ó*   #   • U  H	  oS    v •  M     g7fr!   r   r   s     r   r   rT   Š   rU   r   c              3  ó*   #   • U  H	  oS    v •  M     g7fr'   r   r   s     r   r   rT   Š   rW   r   r)   )r,   ÚrxÚryÚcxÚcys      €€r   ÚoffsetÚtighten_box.<locals>.offsetˆ   sh   ø€ ÜÑ(¡CÓ(Ó(¬3Ñ.A¹SÓ.AÓ+AÑAÀQÑFˆBÜÑ(¡CÓ(Ó(¬3Ñ.A¹SÓ.AÓ+AÑAÀQÑFˆBØ˜‘G ‘> R¨"¡W°¡NÑ2Ð2r   r8   c                ó  >• TU S   U S   S-     Vs/ s H  oS   PM	     nn[        S U 5       5      [        S U 5       5      -   S-  n[        S U 5       5      [        S U 5       5      -   S-  nUT-
  S-  UT-
  S-  -   $ s  snf )Nr   r"   c              3  ó*   #   • U  H	  oS    v •  M     g7fr   r   r   s     r   r   Ú2tighten_box.<locals>.run_offset.<locals>.<genexpr>¡   rU   r   c              3  ó*   #   • U  H	  oS    v •  M     g7fr$   r   r   s     r   r   rb   ¡   rW   r   r%   c              3  ó*   #   • U  H	  oS    v •  M     g7fr!   r   r   s     r   r   rb   ¢   rU   r   c              3  ó*   #   • U  H	  oS    v •  M     g7fr'   r   r   s     r   r   rb   ¢   rW   r   r)   )Úijr5   r,   rZ   r[   rE   r\   r]   s        €€€r   Ú
run_offsetÚtighten_box.<locals>.run_offsetŸ   s˜   ø€ Ø#'¨¨1©°°1±¸±	Ñ#:Ó;Ò#:˜R�a”5Ñ#:ˆCÐ;ÜÑ(¡CÓ(Ó(¬3Ñ.A¹SÓ.AÓ+AÑAÀQÑFˆBÜÑ(¡CÓ(Ó(¬3Ñ.A¹SÓ.AÓ+AÑAÀQÑFˆBØ˜‘G ‘> R¨"¡W°¡NÑ2Ð2ùò <s   ’Bc              3  ó*   #   • U  H	  oS    v •  M     g7fr   r   )r   r5   s     r   r   Útighten_box.<locals>.<genexpr>¦   s   é € Ð6¢o ˜–e¢oùr   )r,   úSequence[Word]ÚreturnÚfloat)rf   zTuple[int, int]rl   rm   )
r   rF   rH   rI   rJ   r-   r*   r   rP   Úextend)r<   r   r=   ÚtargetÚrunsrM   rN   rO   r5   r>   r?   r@   rA   r^   rD   ÚtokensÚbx1Úby1Úbx2Úby2ÚpickedrK   Úfoundrg   rE   r\   r]   s                           @@@r   Útighten_boxrx   i   s¾  ú€ ô �T‹]€FÞØÜ�sÓ"€Dà€DÜ”3�t“9ÖˆØˆÜ�qœ#˜d›)Ö$ˆAØ�d˜1‘g˜a‘jÑ ˆFÜ�6‹{œc &›kÕ)ØÓ#Ø—K‘K°°a¸a¹%±Ó A²¨2 A¤±Ñ AÔBÚó %ñ ö Ø‰ˆ�Ø‘'˜Q‘ ¡¨A¡ˆˆˆB÷	3ð 	3ô
 ”c˜$Ñ+Ó,Ð,à"&§*¡*¤,Ó/¢,˜QŒg�aŽj¡,€FÐ/ÙÓ%š�A£1�a™€FÐ%Ü
ˆ6ƒ{�QƒØð
 Ñ€Cˆˆc�3Ø�C‰i˜1‰_˜s S™y¨A™o€F€BˆØ€FÛˆÜ˜D %Ó(ˆÞÙ÷	3ñ 	3ô �5˜jÑ)‰ˆØ�‰Ñ6 d¨1°1©u¡oÓ6Ö6ñ ô �&‹>ÐùòK !Bùò 0ùÚ%s   ÂF0Ã9F5Ä
F:Ä"F:c                óf  • U u  p#pEU Vs/ s H[  obUS   US   -   S-  s=::  a  U::  d  M  O  M!  X6S   US   -   S-  s=::  a  U::  d  M;  O  M?  US   R                  5       (       d  MY  UPM]     nnU(       d  g[        U5      u  p‰p«[        X‚5      [        X“5      [        X¤5      [        Xµ5      4$ s  snf )a€  The extent of the words printed inside the structural `box`, or None.

The last resort for a cell whose text has no match in the text layer. The
cell cannot be grounded to specific words, but it should never claim more
of the page than the ink it holds, so it shrinks to the words whose centers
sit inside it, on both axes. A box holding no words, as in an image table,
returns None.
r   r%   r"   r(   r0   N)Ústripr-   r+   r*   )r<   r=   r>   r?   r@   rA   r   ÚinsideÚux1Úuy1Úux2Úuy2s               r   Ú
ink_extentr€   ª   s»   € ð �N€BˆBáóÚˆa 1 Q¡4¨!¨A©$¡;°!Ñ"3Õ9°rÑ9‹Ñ9‰¸bÀqÁTÈAÈaÉDÁ[ÐTUÑDUÕ>[ÐY[Ñ>[‹Ñ>[‰Ð`aÐbcÑ`d×`jÑ`j×`l�‘5ð ð ö ØÜ ›Ñ€Cˆcä�‹Lœ#˜c›,¬¨C«´c¸#³lÐCÐCùòs!   ŠB.ªB.®B.ÁB.ÁB.Á&B.c           	     óò  • / n[        U5       Hå  u  pV/ n[        U5       H¾  u  p‰U[        U 5      :  a  U[        X   5      :  a  X   U   OSn
U	b  U
R                  5       (       d  UR                  U	5        MY  U	u  p¼pÞX³U-
  XÓU-
  4n[	        XúU5      =(       d    [        Xò5      nUc  UR                  U	5        Mœ  Uu  nnnnUR                  UUU-
  UUU-
  45        MÀ     UR                  U5        Mç     U$ )a0  Cell boxes moved onto the words each cell prints.

`cell_boxes` arrive and return in Camelot's frame (PDF points,
bottom-left origin), shaped like `cells`. Each cell takes `tighten_box`,
falling back to `ink_extent`. A cell with no box or no text, or one both
return None for, keeps its entry unchanged.
r   )Ú	enumeraterI   rz   rJ   rx   r€   )ÚcellsÚ
cell_boxesr=   Úpage_heightrL   rM   ÚrowÚout_rowrO   r<   r   rr   rs   rt   ru   ÚflippedÚtightÚtx1Úty1Útx2Úty2s                        r   Útighten_cell_boxesrŽ   ¾   sø   € ð ')€CÜ˜JÖ'‰ˆØ(*ˆÜ –n‰FˆAØ"#¤c¨%£j£.°Q¼¸U¹X»Ó5F�5‘8˜A’;ÈBˆDØ‰{ $§*¡*§,¡,Ø—‘˜sÔ#ÙØ!$ÑˆC�cØ¨#Ñ-¨sÀ#Ñ4EÐFˆGÜ ¨uÓ5×S¼ÀGÓ9SˆEØ‰}Ø—‘˜sÖ#à%*Ñ"��S˜#˜sØ—‘  [°3Ñ%6¸¸[È3Ñ=NÐOÖPñ %ð 	�
‰
�7Öñ (ð  €Jr   N)r   Ústrrl   r�   )r,   rk   rl   ÚRect)r<   r�   r=   rk   rl   úList[Tuple[Word, str]])rE   r‘   rK   r�   rl   zList[Tuple[int, int]])r<   r�   r   r�   r=   rk   rl   úOptional[Rect])r<   r�   r=   rk   rl   r’   )
rƒ   zSequence[Sequence[str]]r„   úList[List[Optional[Rect]]]r=   rk   r…   rm   rl   r“   )Ú__doc__Ú
__future__r   Útypingr   r   r   r   rm   r�   r�   ÚWordr:   r4   Ú	maketransr   r   r-   rF   rP   rx   r€   rŽ   r   r   r   Ú<module>r™      sÊ   ðñõ< #ç 2Ó 2àˆU�E˜5 %Ð'Ñ(€ØˆU�E˜5 %¨Ð,Ñ-€ð
 	€ð €
ð —‘Ø˜ C°¸CÈÐTWÐ`cÐloÐx{Ñ|ó€ô
;ô
ôô*ô>ôBDð(Ø"ðà*ðð ðð ð	ð
  õr   