Add regression test for blank page with non-zero MediaBox origin (#1709)
A page whose MediaBox has a non-zero origin (e.g. from PDF Arranger crops) was rendered blank by --force-ocr in v16.12.0, because fix_pagepdf_boxes offset the CropBox with the wrong sign, pushing it entirely outside the image-page MediaBox. The behavior was fixed in v17.0.0 but had no end-to-end test: the existing box tests only assert that ocrmypdf runs, which the blank-page bug passed (it exited 0 with a valid, empty PDF/A). Add a test that renders the output and asserts the visible content survives, parametrized over both the ghostscript and pypdfium rasterizers since the bug reproduced regardless of rasterizer.
This commit is contained in:
+107
-2
@@ -12,9 +12,11 @@ import pikepdf
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from ocrmypdf._exec import ghostscript
|
||||
from ocrmypdf._options import OcrOptions
|
||||
from ocrmypdf._plugin_manager import get_plugin_manager
|
||||
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution
|
||||
from ocrmypdf.pluginspec import GhostscriptRasterDevice
|
||||
|
||||
from .conftest import check_ocrmypdf
|
||||
|
||||
@@ -507,6 +509,110 @@ class TestRasterizerWithNonStandardBoxes:
|
||||
assert pdfium_size == (400, 500), f"pypdfium size: {pdfium_size}"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pdf_with_offset_mediabox_origin(tmp_path):
|
||||
"""Create a single-page PDF whose MediaBox has a non-zero origin.
|
||||
|
||||
Tools that crop/rotate non-destructively (e.g. PDF Arranger) shift the
|
||||
MediaBox origin rather than re-rendering content, producing a MediaBox like
|
||||
``[0, 440, 600, 800]`` with the visible content still inside the box. This
|
||||
fixture reproduces that shape with a full-page gradient so the visible
|
||||
region is unambiguously non-blank. Regression fixture for issue #1709.
|
||||
"""
|
||||
# Full-page gradient so the entire MediaBox region carries content.
|
||||
img = _create_gradient_image(600, 800)
|
||||
img_bytes = BytesIO()
|
||||
img.save(img_bytes, format='PNG')
|
||||
img_bytes.seek(0)
|
||||
|
||||
pdf_bytes = BytesIO()
|
||||
img2pdf.convert(
|
||||
img_bytes.read(),
|
||||
layout_fun=img2pdf.get_fixed_dpi_layout_fun((72, 72)),
|
||||
outputstream=pdf_bytes,
|
||||
**IMG2PDF_KWARGS,
|
||||
)
|
||||
pdf_bytes.seek(0)
|
||||
|
||||
pdf_path = tmp_path / 'offset_mediabox_origin.pdf'
|
||||
with pikepdf.open(pdf_bytes) as pdf:
|
||||
page = pdf.pages[0]
|
||||
# Shift the lower-left y origin so the box is [0, 440, 600, 800]: a
|
||||
# 600x360 visible region whose content lies entirely within the box.
|
||||
page.MediaBox = pikepdf.Array([0, 440, 600, 800])
|
||||
page.CropBox = pikepdf.Array([0, 440, 600, 800])
|
||||
pdf.save(pdf_path)
|
||||
|
||||
return pdf_path
|
||||
|
||||
|
||||
def _nonwhite_fraction(pdf_path, png_path) -> float:
|
||||
"""Rasterize page 1 of pdf_path and return the fraction of non-white pixels."""
|
||||
ghostscript.rasterize_pdf(
|
||||
pdf_path,
|
||||
png_path,
|
||||
raster_device=GhostscriptRasterDevice.PNGGRAY,
|
||||
raster_dpi=Resolution(72, 72),
|
||||
pageno=1,
|
||||
rotation=0,
|
||||
)
|
||||
with Image.open(png_path) as im:
|
||||
gray = im.convert('L')
|
||||
histogram = gray.histogram()
|
||||
total = sum(histogram)
|
||||
# Treat near-white (>= 250) as background; everything else is page content.
|
||||
nonwhite = sum(histogram[:250])
|
||||
return nonwhite / total
|
||||
|
||||
|
||||
class TestOffsetMediaBoxOrigin:
|
||||
"""Regression tests for issue #1709.
|
||||
|
||||
A non-zero MediaBox origin (e.g. from PDF Arranger crops) must not cause
|
||||
--force-ocr to drop the page content and emit a blank page. Both rasterizers
|
||||
are covered because the bug surfaced regardless of which one rendered.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'rasterizer',
|
||||
[
|
||||
'ghostscript',
|
||||
pytest.param(
|
||||
'pypdfium',
|
||||
marks=pytest.mark.skipif(
|
||||
not PYPDFIUM_AVAILABLE, reason="pypdfium2 not installed"
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_force_ocr_preserves_offset_origin_content(
|
||||
self, pdf_with_offset_mediabox_origin, rasterizer, outpdf, tmp_path
|
||||
):
|
||||
"""--force-ocr must preserve content when the MediaBox origin is non-zero."""
|
||||
# Sanity check: the input genuinely has content in its visible region.
|
||||
input_fraction = _nonwhite_fraction(
|
||||
pdf_with_offset_mediabox_origin, tmp_path / 'input.png'
|
||||
)
|
||||
assert input_fraction > 0.5, "test fixture should have a non-blank page"
|
||||
|
||||
check_ocrmypdf(
|
||||
pdf_with_offset_mediabox_origin,
|
||||
outpdf,
|
||||
'--force-ocr',
|
||||
'--rasterizer',
|
||||
rasterizer,
|
||||
'--plugin',
|
||||
'tests/plugins/tesseract_noop.py',
|
||||
)
|
||||
|
||||
# The output page must not be blank: the visible content survives.
|
||||
output_fraction = _nonwhite_fraction(outpdf, tmp_path / 'output.png')
|
||||
assert output_fraction > 0.5, (
|
||||
f"output page is blank (non-white fraction {output_fraction:.3f}); "
|
||||
"content was dropped for a non-zero MediaBox origin (issue #1709)"
|
||||
)
|
||||
|
||||
|
||||
class TestRasterizerWithRotationAndBoxes:
|
||||
"""Test rasterizer + rotation + nonstandard boxes combinations."""
|
||||
|
||||
@@ -671,8 +777,7 @@ class TestRasterizerWithRotationAndBoxes:
|
||||
expected = self._get_expected_size(rotation)
|
||||
|
||||
assert abs(gs_img.size[0] - expected[0]) <= 2, (
|
||||
f"GS width at {rotation}°: {gs_img.size[0]}, "
|
||||
f"expected {expected[0]}"
|
||||
f"GS width at {rotation}°: {gs_img.size[0]}, expected {expected[0]}"
|
||||
)
|
||||
assert abs(gs_img.size[1] - expected[1]) <= 2, (
|
||||
f"GS height at {rotation}°: {gs_img.size[1]}, "
|
||||
|
||||
Reference in New Issue
Block a user