
    j5jPi                       d Z ddlmZ ddlZddlmZ ddlmZ ddlm	Z	 ddl
mZ ddlmZ erdd	lmZ dd
lmZmZmZmZmZmZmZmZ ddlmZ ddlmZ ddlmZ  G d de	      Ze G d d             Z e G d d             Z!e!Z"	 	 	 	 	 	 	 	 ddZ#	 	 	 	 	 	 	 	 ddZ$	 	 	 	 	 	 	 	 	 	 	 	 	 	 d dZ% ejL                  e'      Z(dZ)d!dZ*d"dZ+d#dZ,d$dZ-d#dZ. G d d      Z/y)%z9For grafting text-only PDF pages onto freeform PDF pages.    )annotationsN)suppress)	dataclass)Enum)Path)TYPE_CHECKING)
OcrElement)
DictionaryNameOperatorPagePdfStreamparse_content_streamunparse_content_stream)
PdfContext)ProcessingMode)VECTOR_PAGE_DPIc                      e Zd ZdZdZdZy)
RenderModezControls where the OCR text layer is placed relative to page content.

    ON_TOP: Text layer renders above page content (reserved for future use).
    UNDERNEATH: Text layer renders below page content (current default behavior).
    r      N)__name__
__module____qualname____doc__ON_TOP
UNDERNEATH     E/var/www/html/qr/venv/lib/python3.12/site-packages/ocrmypdf/_graft.pyr   r   "   s     FJr   r   c                  D    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   y)Fpdf2PageInfoz5Information needed to render and graft an fpdf2 page.intpagenor   	hocr_pathfloatdpiautorotate_correctionboolemplaced_pageNr   r   r   r   __annotations__r   r   r    r"   r"   -   s     ?KO	Jr   r"   c                  D    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   y)Fpdf2ParsedPagez+Parsed page data ready for fpdf2 rendering.r#   r$   r	   ocr_treer&   r'   r(   r)   r*   Nr+   r   r   r    r.   r.   8   s!    5K	Jr   r.   c                    |r|} |}|| z
  dz  S )a  Compute rotation needed to align text layer with page content.

    Args:
        content_rotation: Original page /Rotate value (degrees).
        autorotate_correction: Rotation applied during rasterization (degrees).
        emplaced_page: Whether the page content was replaced with rasterized image.

    Returns:
        Rotation in degrees to apply to text layer to align with content.
    h  r   )content_rotationr(   r*   text_rotations       r    _compute_text_misalignmentr4   G   s#     0)M,,33r   c                    |r|} | |z
  dz  S )a]  Compute final page /Rotate value after grafting.

    Args:
        content_rotation: Original page /Rotate value (degrees).
        autorotate_correction: Rotation applied during rasterization (degrees).
        emplaced_page: Whether the page content was replaced with rasterized image.

    Returns:
        Final /Rotate value for the page.
    r1   r   )r2   r(   r*   s      r    _compute_page_rotationr6   [   s     044;;r   c                   ddl m} | |}	} |       j                  | dz  |	 dz        }
 |       j                  |dz  |dz        } |       j                  ||      } |       j                  | dz        }|dv r|	|}	}|r||z  nd}|	r||	z  nd} |       j	                  ||      }|
|z  |z  |z  |z  } |       }||k(  ry|S )a  Build transformation matrix to align text layer with page content.

    Always computes the full CTM to handle non-zero page origins (e.g.,
    JSTOR PDFs with MediaBox like [0, 100, 595, 982]) and minor scale
    differences due to DPI rounding.

    Args:
        text_width: Width of text layer mediabox.
        text_height: Height of text layer mediabox.
        page_width: Width of target page mediabox.
        page_height: Height of target page mediabox.
        page_origin_x: X origin of target page mediabox.
        page_origin_y: Y origin of target page mediabox.
        text_rotation: Rotation in degrees (clockwise) to apply to text layer.

    Returns:
        pikepdf.Matrix transformation matrix, or None if identity.
    r   )Matrix   r1   )Z   i  g      ?N)pikepdfr8   
translatedrotatedscaled)
text_widthtext_height
page_widthpage_heightpage_origin_xpage_origin_yr3   r8   wtht	translateuntranslatecornerrotatescale_xscale_yscalectmidentitys                      r    _build_text_layer_ctmrP   m   s    6 B ##RC!GbS1W5I(%%j1nkAoFKX  >F X}ns23F 	!RB "$j2oG"$kB#GHOOGW-E
f
u
${
2V
;C xH
hJr   d   c                0    || vrt        i       | |<   | |   S )N)r
   )objnames     r    _ensure_dictionaryrU      s     3rND	t9r   c                h   g }d}d}g }g }t        |d      D ]  \  }}|t        d      k(  r|d   }|t        d      k(  r|j                  |       |t        d      k(  r(t        t              5  |j                         }d d d        |s8|t        d      k(  rd}|j                  ||f       |j                  ||f       |j                  ||f       |t        d	      k(  sd}|d
k7  r|j                  |       |j                           t        |      }	t        | |	      |_
        y # 1 sw Y   xY w)NFr    TrqQBTTET   )r   r   appendr   
IndexErrorpopextendclearr   r   Contents)
pdfpagestreamin_text_objrender_moderender_mode_stacktext_objectsoperandsoperatorcontent_streams
             r    strip_invisible_textrn      s5   FKKL24< %(x~%"1+Kx}$$$[1x}$*% 6/3356 8D>)"##Xx$89x238 458D>)#!#MM,/""$1%4 ,F3N3/DM%6 6s   ,D((D1	c                R   	 | j                   j                  t        j                        }t	        |t
              rt        j                  |vry|t        j                  = t        j                  d       t        |      dk(  r| j                   `y# t        t        t        f$ r Y yw xY w)a  Discard an embedded Adobe full-text search index from the catalog.

    Adobe Acrobat can embed a full-text search index in the document catalog at
    ``/Root/PieceInfo/SearchIndex``. It is built from the page text, and only
    Acrobat reads it; other viewers ignore it and search the text on the fly.
    Any change to the PDF invalidates the index, so once OCRmyPDF rewrites the
    document (editing the text layer, rasterizing, optimizing) a retained index
    would be stale and return incorrect search results in Acrobat. We cannot
    update this vendor-private data, so we discard it; modern viewers rebuild a
    search index on demand. Returns True if the catalog was modified.
    Fz~Discarded embedded text search index (/Root/PieceInfo/SearchIndex) because the PDF was rewritten; it would otherwise be stale.r   T)Rootgetr   	PieceInfo
isinstancer
   SearchIndexlogdebuglenKeyError	TypeErrorAttributeError)rd   	pieceinfos     r    discard_text_search_indexr|      s    HHLL0	)Z0D4D4DI4Ud&&'		+	
 y>Q"i0 s   AB A B B&%B&c                    d}| j                   D ]7  }|j                  }t        j                  |v s"|t        j                  = |dz  }9 |rt        j                  d|       |S )u-  Discard embedded per-page thumbnail images.

    A page object may carry an optional ``/Thumb`` image XObject — a miniature
    rendering of the page (ISO 32000-2, 12.3.4). It is only a navigation aid and
    modern viewers generate page thumbnails on demand. OCRmyPDF alters page
    appearance (deskew, clean, rasterize, re-render) and plugins may edit pages
    arbitrarily, so any retained thumbnail would be stale and misrepresent its
    page. We discard them; viewers rebuild thumbnails as needed. Returns the
    number of thumbnails removed.
    r   r   znDiscarded %d embedded page thumbnail(s) (/Thumb) because the PDF was rewritten; they would otherwise be stale.)pagesrS   r   Thumbru   rv   )rd   removedre   pageobjs       r    discard_page_thumbnailsr      sf     G		 ((:: 

#qLG	
 		<	

 Nr   c                   d}	 t         j                  | j                  v r| j                  `d}t         j                  | j                  v r| j                  `d}| j                  D ]<  }t         j
                  |j                  v s |j                  t         j
                  = d}> 	 |rt        j                  d       |S # t        t        t        f$ r |cY S w xY w)a?  Discard the logical structure (tagged-PDF) tree from the document.

    The structure tree (``/Root/StructTreeRoot``, ``/Root/MarkInfo``) maps
    marked content in the page content streams to semantic elements via MCIDs.
    When OCRmyPDF rasterizes pages (force) or strips and rewrites the text layer
    (redo), those MCIDs are destroyed or renumbered, leaving the tree dangling
    and inconsistent with the new content. We cannot rebuild it to match, so we
    discard it; the page-level ``/StructParents`` keys go too. Returns True if
    the catalog was modified.
    FTzvDiscarded the logical structure tree (/Root/StructTreeRoot) because the PDF was re-OCR'd; it would otherwise be stale.)r   StructTreeRootrp   MarkInfor~   StructParentsrS   rx   ry   rz   ru   rv   )rd   modifiedre   s      r    discard_structure_treer     s     H#((*'H==CHH$!HII 	 D!!TXX-HHT//0	  		I	
 O i0 s   A?B= B= =CCc                  b    e Zd ZdZd
dZ	 	 	 	 	 	 	 	 	 	 ddZd Zd Zd ZddZ		 	 	 	 	 	 ddZ
y	)
OcrGrafterz2Manages grafting text-only PDFs onto regular PDFs.c                ^   || _         |j                  | _        t        j                  | j                        | _        |j                  | _        |j                  d      | _        d| _	        t        j                  | _        |j                  j                  }|dk(  | _        g | _        g | _        y )Nzgraft_layers.pdfr   sandwich)contextorigin	path_baser   openpdf_basepdfinfoget_pathoutput_fileemplacementsr   r   rh   optionspdf_rendereruse_sandwich_rendererfpdf2_hocr_pagesfpdf2_parsed_pages)selfr   r   s      r    __init__zOcrGrafter.__init__2  s     0"++,>?%00 33%1Z%?" 689;r   c          
        | j                   j                  j                  t        j                  k(  r^| j                   j                  }|j
                  r||j
                  v r-t        | j                  | j                  j
                  |          y|r|rt        d      d}| j                  |   j                  }|rt        |      j                         nd}	|	|	| j                  k7  rt        j                  d       t!        j"                  |	      5 }
| xj$                  dz  c_        |
j
                  d   }| j                  j
                  j'                  |       | j                  j
                  d   }| j                  j
                  |   j)                  |t*        j,                  f       | j                  j
                  d= ddd       d	}| j.                  rP|rMt1        |||      }| j3                  |||
       t5        |||      }|| j                  j
                  |   _        yy|rN| j8                  j'                  t;        ||||| j                  |   j<                  j?                                      |rO| j@                  j'                  tC        ||||| j                  |   j<                  j?                                      yy# 1 sw Y   
xY w)a  Graft OCR output onto a page of the base PDF.

        Args:
            pageno: Zero-based page number.
            image: Path to the visible page image PDF, or None if not replacing.
            ocr_output: Path to OCR output file. For fpdf2 renderer this is an
                hOCR file; for sandwich renderer this is a text-only PDF.
            ocr_tree: OCR tree for fpdf2 renderer.
            autorotate_correction: Orientation correction in degrees (0, 90, 180, 270).
        Nz>Cannot specify both ocr_output and ocr_tree for fpdf2 rendererFzEmplacement updater   r   )retainT)r$   textpdfr3   )r/   r$   r(   r*   r'   )r%   r$   r(   r*   r'   )"r   r   moder   
strip_textr~   rn   r   
ValueErrorr   rotationr   resolver   ru   rv   r   r   r   r^   emplacer   Parentr   r4   _graft_sandwich_text_layerr6   Rotater   r.   r'   	to_scalarr   r"   )r   r$   image
ocr_outputr/   r(   r   r*   r2   
path_image	pdf_imageforeign_image_pagelocal_image_pagetext_misalignedpage_rotations                  r    
graft_pagezOcrGrafter.graft_pageF  s   & <<$$(A(AA ll**G==Fgmm$;$T]]DMM4G4G4OP(P  <</88.3T%[((*
!jDNN&B II*+*% ,!!Q&!%.__Q%7"##**+=>#'==#6#6r#: ##F+33$dkk^ 4  MM''+, !M%%"<$&;]# //!&"1 0 
 !7$&;]! 6C##F+2 $ ''..#!)%.C&3 LL044>>@ %%,,!",%.C&3 LL044>>@ S, ,s   B3KKc                R   | j                   r| j                  rJ d       | j                   r| j                         }|| _        | j                  r| j                          t	        | j
                         t        | j
                         | j                  j                  j                  t        j                  t        j                  fv rt        | j
                         | j
                  j                  | j                         | j
                  j!                          | j                  S )Nz&Can't have both hocr and ocrtree pages)r   r   _parse_hocr_pages_render_and_graft_fpdf2_pagesr|   r   r   r   r   r   r   forceredor   saver   close)r   parsed_pagess     r    finalizezOcrGrafter.finalize  s    ))d.E.E 	
4	
F   113L&2D#""..0!$--0.<<$$)=)=~?R?R(SS"4==14++,r   c           
        ddl m} t        j                  dt	        | j
                               g }| j
                  D ]  }|j                  j                         j                  dk(  r+ ||j                        }|j                         }|j                  xs |j                  xs t        t              }|j                  t        |j                  |||j                   |j"                                |S )zARender all pages to multi-page PDF with shared fonts, then graft.r   )
HocrParserz Parsing %d pages with HocrParser)r$   r/   r'   r(   r*   )"ocrmypdf.hocrtransform.hocr_parserr   ru   inforw   r   r%   statst_sizeparser'   r&   r   r^   r.   r$   r(   r*   )r   r   
pages_data	page_infoparserr/   effective_dpis          r    r   zOcrGrafter._parse_hocr_pages  s    A.%%&	
 -/
.. 	I""'')11Q6  	 3 34F||~H %LLSIMMSU?=SM$++%%*3*I*I"+"9"9	* r   c                   t        t              j                  dz  }| j                  j	                  d      }ddlm} ddlm}  ||      }| j                  D cg c]%  }|j                  |j                  |j                  f' }} |||d      }|j                  |       t        j                  |      5 }	t!        | j                        D ]  \  }
}|	j"                  |
   }| j$                  |j                     j&                  }t)        ||j*                  |j,                        }| j/                  |j                  ||       t1        ||j*                  |j,                        }|| j2                  j"                  |j                     _         	 d d d        | j                  j6                  j8                  s)t;        t<              5  |j?                          d d d        y y c c}w # 1 sw Y   XxY w# 1 sw Y   y xY w)Ndatazfpdf2_multipage.pdfr   )MultiFontManager)Fpdf2MultiPageRendererT)r   multi_font_managerinvisible_text) r   __file__parentr   r   ocrmypdf.fontr   ocrmypdf.fpdf_rendererr   r   r$   r/   r'   renderr   r   	enumerater~   r   r   r4   r(   r*   _graft_fpdf2_text_layerr6   r   r   r   keep_temporary_filesr   FileNotFoundErrorunlink)r   font_dirmulti_page_pdf_pathr   r   r   parsedrenderer_pages_datarendererpdf_textidx	text_pager2   r   r   s                  r    r   z(OcrGrafter._render_and_graft_fpdf2_pages  s   >((61 #ll334IJ2A-h7 11
 ]]FOOVZZ8
 
 **1
 	+, XX)* 	Jh()@)@A JV$NN3/	#'<<#>#G#G "<$00((#
 ,,V]]IW 6$00((!
 =J##FMM29#J	J* ||##88+, -#**,- - 9E
	J 	J,- -s   *G!7CG&G2&G/2G;c                   ddl m} | j                  j                  |   }|j                  j                         } ||j                  D cg c]  }t        |       c}      }t        |d         t        |d         z
  }	t        |d         t        |d         z
  }
|j                  }t        |d         t        |d         z
  }t        |d         t        |d         z
  }t        |j                  t        j                        }t        |t        j                        }t        j                  d      }| j                  j                  |      }|||<   t        j                  |_        t        j                   |_        d|_        ||_        t)        |d      r9|j                  r,t        |t        j                        }t        j*                  |j                  v rmt        |t        j*                        }|j                  t        j*                     }|j-                         D ]#  \  }}| j                  j/                  |      ||<   % t        j0                  |j                  v rmt        |t        j0                        }|j                  t        j0                     }|j-                         D ]#  \  }}| j                  j/                  |      ||<   % t3        |	|
||t        |d         t        |d         |      }|d
|j5                         z  d|z  z   dz   }ndd|z  z   dz   }t7        | j                  |      }| j8                  j:                  j<                  t>        j@                  k(  rtC        | j                  |       |jE                          |jG                  || jH                  tJ        jL                  k(         |jE                          y	c c}w )at  Graft a single text page onto the base PDF.

        Similar to existing _graft_text_layer but works with
        already-rendered pikepdf Page instead of file path.

        Args:
            pageno: Zero-based page number.
            text_page: The text-only PDF page to graft.
            text_rotation: Rotation to apply to align text with content (degrees).
        r   )Arrayr9   r]   r   OCR-prefix	ResourcesN   q %s cm
   %s Do
s   Q
   q
   
Q
prepend)'r;   r   r   r~   rc   
read_bytesmediaboxr&   rU   rS   r   r   XObjectrandommake_streamTypeFormSubtypeFormTypeBBoxhasattrFontitemscopy_foreign	ExtGStaterP   encoder   r   r   r   r   r   rn   contents_coalescecontents_addrh   r   r   )r   r$   r   r3   r   	base_pagetext_contentsxr   rE   rF   base_mediaboxwphpbase_resources
base_xobjstext_xobj_namexobjxobj_resources
xobj_fonts
text_fonts	font_namefont_objxobj_extstatestext_extstatesgs_namegs_objrN   pdf_draw_xobjnew_text_layers                                 r    r   z"OcrGrafter._graft_fpdf2_text_layer
  sK    	"MM''/	 "**557 I,>,>?q%(?@8A;%"448A;%"44 "**=#$u]1-='>>=#$u]1-='>> ,IMM4>>J'E
F3}}((7%)
>"LL	yy!	 9k*y/B/B/dnnEN yyI////		J
&00;
+5+;+;+= Q'Ix,0MM,F,Fx,PJy)Q ~~!4!44!3NDNN!S!*!4!4T^^!D'5';';'= QOGV.2mm.H.H.PN7+Q $-"#-"#
 ?

,n1LMPVV  #j>&ABXMM}= <<$$(;(;; 	: 	##%D$4$4
8M8M$M 	 	
 	##%M @s   
Oc               V   ddl m} t        j                  d       t	        |      j                         j                  dk(  ry	 t        j                  |      5 }|j                  d   j                  j                         }| j                  j                  |   }|j                  d   j                  j                  t        j                   t#                     }d}	d}
dD ]>  }|j                  |d      }|t        |      }
| j                  j%                  |      }	 n |j                  d   j&                  }t)        |d         t)        |d         z
  }t)        |d         t)        |d         z
  }|j&                  }t)        |d         t)        |d         z
  }t)        |d         t)        |d         z
  }t+        ||||t)        |d         t)        |d         |      }t        j                  d	|       t-        |j.                  t        j                        }t-        |t        j0                        }t        j2                  d
      }| j                  j5                  |      }|||<   t        j0                  |_        t        j8                  |_        d|_        ||_        |
?|	=t-        |t        j                        }t-        |t        j                         }|
|vr|	||
<   |d|jA                         z  d|z  z   dz   }ndd|z  z   dz   }tC        | j                  |      }| jD                  jF                  jH                  tJ        jL                  k(  rtO        | j                  |       |jQ                          |jS                  || jT                  tV        jX                  k(         |jQ                          |
I|	Gt-        |j.                  t        j                        }t-        |t        j                         }|
|vr|	||
<   ddd       y# 1 sw Y   yxY w# tZ        |f$ r Y yw xY w)zGraft a pre-rendered text-only PDF onto the base PDF.

        This is used by the sandwich renderer which generates PDFs directly
        from Tesseract rather than going through hOCR.
        r   )PdfErrorzGrafting sandwich text layerN)z/f-0-0z/F1r9   r]   r   zGrafting with ctm %rr   r   r   r   r   r   r   ).r;   r  ru   rv   r   r   r   r   r   r~   rc   r   r   r   rq   r   r   r
   r   r   r&   rP   rU   rS   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rn   r   r   rh   r   r   r   )r   r$   r   r3   r  r   pdf_text_contentsr   pdf_text_fontsfontfont_keyfpdf_text_fontr   rE   rF   r  r  r  rN   r  r  r  r  r  r	  r  r  page_resources
page_fontss                                 r    r   z%OcrGrafter._graft_sandwich_text_layere  s    	%		01=''1,T	'" P4h$,NN1$5$>$>$I$I$K! MM//7	 "*!2!<!<!@!@IIz|" * A$2$6$6q$$?M$0#'7#}}99-H $>>!,558A;'%*<<8A;'%*<< ) 2 2=+,u]15E/FF=+,u]15E/FF ,-*+-*+! 		0#6 "4IMM4>>!R/M
!%F!;}}001BC-1
>* LL	#yy !)	 'D,<%7dnn%MN!3NDII!NJz1/3
8,?%

4%68"# " %+j>.I$JX$UM!'}!E<<'',,0C0CC(	B++-&&"D,<,<
@U@U,U '  ++- 'D,<%7	t~~%VN!3NDII!NJz1/3
8,aP4 P4 P4b "8, 		s8   P BP7LPP PP P P('P(N)r   r   )
r$   r#   r   Path | Noner   r  r/   zOcrElement | Noner(   r#   )r$   r#   r   r   r3   r#   )r$   r#   r   r   r3   r#   )r   r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   /  s    <<(Y Y 	Y
  Y $Y  #Yv , D/-bY&vf f 	f
 fr   r   )r2   r#   r(   r#   r*   r)   returnr#   )r?   r&   r@   r&   rA   r&   rB   r&   rC   r&   rD   r&   r3   r#   )rS   zDictionary | StreamrT   r   )rd   r   re   r   )rd   r   r  r)   )rd   r   r  r#   )0r   
__future__r   logging
contextlibr   dataclassesr   enumr   pathlibr   typingr   ocrmypdf.hocrtransformr	   r;   r
   r   r   r   r   r   r   r   ocrmypdf._jobcontextr   ocrmypdf._optionsr   ocrmypdf._pipeliner   r   r"   r.   Fpdf2DirectPager4   r6   rP   	getLoggerr   ru   MAX_REPLACE_PAGESrU   rn   r|   r   r   r   r   r   r    <module>r-     sX   @ "   !    1	 	 	 , , .        "44254FJ44(<<25<FJ<<$777 7 	7
 7 7 7t g! "0J<4B\ \r   