diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58739755..22e51a7f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -194,6 +194,7 @@ jobs: choco install --yes --no-progress tesseract choco install --yes --no-progress --ignore-checksums ghostscript --version 9.56.1 choco install --yes --no-progress poppler --version=25.11.0 + choco install --yes --no-progress noto - name: Install Python packages run: | diff --git a/tests/test_json_serialization.py b/tests/test_json_serialization.py index d7ee6a78..3de76ed5 100644 --- a/tests/test_json_serialization.py +++ b/tests/test_json_serialization.py @@ -2,7 +2,7 @@ import multiprocessing from io import BytesIO -from pathlib import Path +from pathlib import Path, PurePath import pytest @@ -98,8 +98,8 @@ def test_json_serialization_multiprocessing(): for result_json in results: result = json.loads(result_json) - assert result['input_file'] == '/test/input.pdf' - assert result['output_file'] == '/test/output.pdf' + assert PurePath(result['input_file']) == PurePath('/test/input.pdf') + assert PurePath(result['output_file']) == PurePath('/test/output.pdf') assert result['languages'] == ['eng', 'deu'] assert result['optimize'] == 2 assert result['tesseract_timeout'] == 120.0 diff --git a/tests/test_multilingual_direct.py b/tests/test_multilingual_direct.py index fd130141..e1d76429 100644 --- a/tests/test_multilingual_direct.py +++ b/tests/test_multilingual_direct.py @@ -11,6 +11,7 @@ This tests the fpdf2 renderer with various language groups: - Devanagari (Hindi, Sanskrit) """ +import shutil import subprocess from pathlib import Path @@ -23,6 +24,26 @@ from ocrmypdf.hocrtransform.hocr_parser import HocrParser RESOURCES = Path(__file__).parent / "resources" +@pytest.fixture +def pdftotext(): + """Return a function to extract text from PDF using pdftotext. + + Skips the test if pdftotext is not available. + """ + pdftotext_path = shutil.which('pdftotext') + if pdftotext_path is None: + pytest.skip("pdftotext not available") + + def extract_text(pdf_path: Path) -> str: + return subprocess.check_output( + ['pdftotext', '-enc', 'UTF-8', str(pdf_path), '-'], + text=True, + encoding='utf-8', + ) + + return extract_text + + @pytest.fixture def font_dir(): """Return path to font directory.""" @@ -48,7 +69,9 @@ class TestLatinScript: """Return path to Latin HOCR test file.""" return RESOURCES / "latin.hocr" - def test_render_latin_basic(self, latin_hocr, multi_font_manager, tmp_path): + def test_render_latin_basic( + self, latin_hocr, multi_font_manager, tmp_path, pdftotext + ): """Test rendering Latin script with various diacritics.""" parser = HocrParser(latin_hocr) page = parser.parse() @@ -76,11 +99,7 @@ class TestLatinScript: assert output_pdf.stat().st_size > 0 # Extract text and verify - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) # English words assert 'quick' in text or 'brown' in text or 'fox' in text @@ -122,7 +141,9 @@ class TestArabicScript: """Return path to Arabic HOCR test file.""" return RESOURCES / "arabic.hocr" - def test_render_arabic_basic(self, arabic_hocr, multi_font_manager, tmp_path): + def test_render_arabic_basic( + self, arabic_hocr, multi_font_manager, tmp_path, pdftotext + ): """Test rendering Arabic script text.""" parser = HocrParser(arabic_hocr) page = parser.parse() @@ -145,11 +166,7 @@ class TestArabicScript: assert output_pdf.stat().st_size > 0 # Extract text and verify Arabic content - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) # Arabic words: مرحبا بالعالم (Hello world) assert 'مرحبا' in text or 'بالعالم' in text @@ -204,7 +221,7 @@ class TestCJKScript: """Return path to CJK HOCR test file.""" return RESOURCES / "cjk.hocr" - def test_render_cjk_basic(self, cjk_hocr, multi_font_manager, tmp_path): + def test_render_cjk_basic(self, cjk_hocr, multi_font_manager, tmp_path, pdftotext): """Test rendering CJK script text.""" if not _cjk_font_works(multi_font_manager): pytest.skip("CJK font not available or corrupted") @@ -237,11 +254,7 @@ class TestCJKScript: assert output_pdf.stat().st_size > 0 # Extract text and verify CJK content - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) # Chinese: 你好 世界 (Hello world) assert '你好' in text or '世界' in text @@ -287,7 +300,7 @@ class TestDevanagariScript: return RESOURCES / "devanagari.hocr" def test_render_devanagari_basic( - self, devanagari_hocr, multi_font_manager, tmp_path + self, devanagari_hocr, multi_font_manager, tmp_path, pdftotext ): """Test rendering Devanagari script text.""" parser = HocrParser(devanagari_hocr) @@ -311,11 +324,7 @@ class TestDevanagariScript: assert output_pdf.stat().st_size > 0 # Extract text and verify Devanagari content - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) # Hindi: नमस्ते दुनिया (Hello world) assert 'नमस्ते' in text or 'दुनिया' in text @@ -356,7 +365,7 @@ class TestMultilingual: return RESOURCES / "multilingual.hocr" def test_render_multilingual_hocr_basic( - self, multilingual_hocr, multi_font_manager, tmp_path + self, multilingual_hocr, multi_font_manager, tmp_path, pdftotext ): """Test rendering multilingual HOCR file with English and Arabic text.""" parser = HocrParser(multilingual_hocr) @@ -384,11 +393,7 @@ class TestMultilingual: assert output_pdf.stat().st_size > 0 # Extract text from PDF - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) # Verify both English and Arabic text are present assert 'English' in text or 'Text' in text or 'Here' in text @@ -422,7 +427,7 @@ class TestMultilingual: assert output_pdf.stat().st_size > 0 def test_multilingual_invisible_text( - self, multilingual_hocr, multi_font_manager, tmp_path + self, multilingual_hocr, multi_font_manager, tmp_path, pdftotext ): """Test rendering with invisible text (default OCR mode).""" parser = HocrParser(multilingual_hocr) @@ -441,11 +446,7 @@ class TestMultilingual: assert output_pdf.exists() # Text should still be extractable even though invisible - text = subprocess.check_output( - ['pdftotext', '-enc', 'UTF-8', str(output_pdf), '-'], - text=True, - encoding='utf-8', - ) + text = pdftotext(output_pdf) assert len(text.strip()) > 0 def test_multilingual_font_selection(self, multilingual_hocr, multi_font_manager): diff --git a/tests/test_page_boxes.py b/tests/test_page_boxes.py index 3737f482..ad428a6f 100644 --- a/tests/test_page_boxes.py +++ b/tests/test_page_boxes.py @@ -68,7 +68,7 @@ def test_media_box( with pikepdf.open(outdir / 'processed.pdf') as pdf: page = pdf.pages[0] - assert page['/MediaBox'] == crop_expected + assert page.mediabox == crop_expected cropbox_testdata = [ @@ -122,4 +122,4 @@ def test_crop_box( with pikepdf.open(outdir / 'processed.pdf') as pdf: page = pdf.pages[0] - assert page.CropBox == crop_expected + assert page.cropbox == crop_expected diff --git a/tests/test_pipeline_generate_ocr.py b/tests/test_pipeline_generate_ocr.py index c7de5d38..f2b03eda 100644 --- a/tests/test_pipeline_generate_ocr.py +++ b/tests/test_pipeline_generate_ocr.py @@ -13,7 +13,7 @@ import dataclasses from pathlib import Path from unittest.mock import MagicMock, patch -from ocrmypdf import OcrElement +from ocrmypdf import BoundingBox, OcrElement class TestOcrEngineDirect: @@ -25,7 +25,7 @@ class TestOcrEngineDirect: assert hasattr(_pipeline, 'ocr_engine_direct') - def test_ocr_engine_direct_returns_tuple(self): + def test_ocr_engine_direct_returns_tuple(self, tmp_path): """ocr_engine_direct should return (OcrElement, Path) tuple.""" from ocrmypdf._pipeline import ocr_engine_direct @@ -34,11 +34,11 @@ class TestOcrEngineDirect: mock_engine = MagicMock() mock_engine.supports_generate_ocr.return_value = True mock_engine.generate_ocr.return_value = ( - OcrElement(ocr_class='ocr_page', bbox=(0, 0, 100, 100)), + OcrElement(ocr_class='ocr_page', bbox=BoundingBox(0, 0, 100, 100)), "test text", ) mock_context.plugin_manager.get_ocr_engine.return_value = mock_engine - mock_context.get_path.return_value = Path("/tmp/test.txt") + mock_context.get_path.return_value = tmp_path / Path("test.txt") mock_context.pageno = 0 with patch('builtins.open', MagicMock()):