1111from typing import Union , List , Dict , Tuple
1212from multiprocessing import Process , Queue , Value
1313
14+ ############################
15+ # MONKEY PATCH BEGINNING
16+ ############################
17+ # REMOVE MONKEY PATCH CODE IF THIS PR IS ACCEPTED: https://github.com/ocrmypdf/OCRmyPDF/pull/1493
18+ from ocrmypdf .hocrtransform import HocrTransform
19+ from pikepdf .canvas import TextDirection
20+
21+ original_get_text_direction = HocrTransform ._get_text_direction
22+
23+ def fixed_get_text_direction (self , par ):
24+ """Get the text direction of the paragraph with None check."""
25+ if par is None :
26+ return TextDirection .LTR # Default to left-to-right
27+
28+ return (
29+ TextDirection .RTL
30+ if par .attrib .get ('dir' , 'ltr' ) == 'rtl'
31+ else TextDirection .LTR
32+ )
33+
34+ HocrTransform ._get_text_direction = fixed_get_text_direction
35+ ############################
36+ # MONKEY PATCH ENDING
37+ ############################
38+
1439class OCRProcessor (ABC ):
1540 def __init__ (self , zoom : int = 2 , progress_queue : Queue = None ):
1641 self .zoom = zoom
@@ -33,7 +58,7 @@ def convert_page_to_image(self, page) -> Image.Image:
3358 def process_page (self , page_num : int , pdf_path : str ) -> Tuple [int , str ]:
3459 """
3560 Process a single page from a PDF file.
36- Each implementation should handle opening and closing the PDF.
61+ Each OCR backend should handle opening and closing the PDF.
3762 """
3863 pass
3964
@@ -113,8 +138,7 @@ def initialize(self):
113138
114139 script_dir = Path (__file__ ).resolve ().parent
115140
116- # control temporary directory
117- # necessary in case the default temp locations don't have write permission
141+ # specify temp dir since sometimes default locations don't have write permission
118142 self .temp_dir = script_dir / "temp_ocr"
119143 self .temp_dir .mkdir (exist_ok = True )
120144
@@ -168,7 +192,8 @@ def process_document(self, pdf_path: Path, output_path: Path = None):
168192 output_pdf .save (output_path )
169193 output_pdf .close ()
170194
171- # final cleanup
195+ self .optimize_final_pdf (pdf_path , output_path )
196+
172197 self .cleanup_temp_pdfs ()
173198
174199 if self .progress_queue :
@@ -198,12 +223,24 @@ def process_page(self, page_num: int, pdf_path: str) -> Tuple[int, str]:
198223 pil_image = Image .open (BytesIO (pix .tobytes ("png" )))
199224
200225 api .SetImage (pil_image )
226+
201227 hocr_text = api .GetHOCRText (0 )
202228
203- with tempfile .NamedTemporaryFile (delete = False , suffix = ".hocr" , dir = self .temp_dir ) as hocr_temp :
204- hocr_output = hocr_temp .name
229+ # # DEBUG
230+ # if not hocr_text.strip():
231+ # print(f"Warning: No text detected on page {page_num}. Skipping HOCR file creation.")
232+ # else:
233+ # hocr_output = f"{self.temp_dir}/page_{page_num}.hocr"
234+ # Path(hocr_output).write_text(hocr_text, encoding="utf-8")
235+ # file_size = Path(hocr_output).stat().st_size
236+ # print(f"HOCR file saved: {hocr_output} (Size: {file_size} bytes)")
237+
238+ # name the hocr file by page number of the pdf
239+ hocr_output = f"{ self .temp_dir } /page_{ page_num } .hocr"
205240 Path (hocr_output ).write_text (hocr_text , encoding = "utf-8" )
206241
242+ # print(f"Processing page {page_num}, HOCR file saved: {hocr_output}") # DEBUG
243+
207244 fd , text_pdf = tempfile .mkstemp (suffix = ".pdf" , dir = self .temp_dir )
208245 os .close (fd )
209246
@@ -228,7 +265,7 @@ def process_page(self, page_num: int, pdf_path: str) -> Tuple[int, str]:
228265 overlay = True
229266 )
230267
231- Path (hocr_output ).unlink (missing_ok = True ) # comment to keep the hocr file for DEBUG
268+ Path (hocr_output ).unlink (missing_ok = True ) # DEBUG comment out to keep the hocr files
232269
233270 for _ in range (10 ):
234271 try :
@@ -244,6 +281,41 @@ def process_page(self, page_num: int, pdf_path: str) -> Tuple[int, str]:
244281
245282 return page_num , temp_pdf_path
246283
284+ def optimize_final_pdf (self , original_pdf_path : Path , ocr_pdf_path : Path ) -> None :
285+ """Post-process the PDF to match original dimensions and apply optimization."""
286+ # print(f"Optimizing OCR'd PDF: {ocr_pdf_path}")
287+
288+ with fitz .open (original_pdf_path ) as original_doc :
289+ orig_pages = []
290+ for page in original_doc :
291+ orig_pages .append ({
292+ 'width' : page .rect .width ,
293+ 'height' : page .rect .height ,
294+ 'mediabox' : page .mediabox ,
295+ 'cropbox' : page .cropbox if hasattr (page , 'cropbox' ) else None
296+ })
297+
298+ temp_path = str (ocr_pdf_path ) + ".optimized"
299+
300+ with fitz .open (ocr_pdf_path ) as ocr_doc :
301+ for i , page in enumerate (ocr_doc ):
302+ if i < len (orig_pages ):
303+ orig = orig_pages [i ]
304+ page .set_mediabox (orig ['mediabox' ])
305+ if orig ['cropbox' ]:
306+ page .set_cropbox (orig ['cropbox' ])
307+
308+ ocr_doc .save (
309+ temp_path ,
310+ garbage = 4 ,
311+ deflate = True ,
312+ clean = True ,
313+ linear = True
314+ )
315+
316+ os .replace (temp_path , ocr_pdf_path )
317+ # print(f"PDF optimization complete. Check final file size.")
318+
247319 def cleanup_temp_pdfs (self ):
248320 if self .temp_dir is None :
249321 return
0 commit comments