Fix OCR text displacement on PDFs with non-zero MediaBox origins

_build_text_layer_ctm() returned None when text_rotation was 0,
skipping the origin translation needed for pages with non-zero
MediaBox origins (e.g. JSTOR PDFs with [0, 100, 595, 982]). This
caused the text layer to be offset by the page origin amount.

Always compute the full CTM and only return None when the result
is the identity matrix.

Fixes #1630
This commit is contained in:
James R. Barlow
2026-02-17 23:34:33 -08:00
parent 5890d1855e
commit 10b71937c4
2 changed files with 25 additions and 25 deletions
+13 -5
View File
@@ -117,6 +117,10 @@ def _build_text_layer_ctm(
):
"""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.
@@ -127,11 +131,8 @@ def _build_text_layer_ctm(
text_rotation: Rotation in degrees (clockwise) to apply to text layer.
Returns:
pikepdf.Matrix transformation matrix, or None if no rotation needed.
pikepdf.Matrix transformation matrix, or None if identity.
"""
if text_rotation == 0:
return None
from pikepdf import Matrix
wt, ht = text_width, text_height
@@ -153,7 +154,14 @@ def _build_text_layer_ctm(
scale_y = page_height / ht if ht else 1.0
scale = Matrix().scaled(scale_x, scale_y)
return translate @ rotate @ scale @ untranslate @ corner
ctm = translate @ rotate @ scale @ untranslate @ corner
# Return None if the result is effectively identity
identity = Matrix()
if ctm == identity:
return None
return ctm
log = logging.getLogger(__name__)