Skip to content

ROB: Tolerate malformed /FontBBox when building font descriptors#3895

Open
metsw24-max wants to merge 3 commits into
py-pdf:mainfrom
metsw24-max:font-bbox-malformed
Open

ROB: Tolerate malformed /FontBBox when building font descriptors#3895
metsw24-max wants to merge 3 commits into
py-pdf:mainfrom
metsw24-max:font-bbox-malformed

Conversation

@metsw24-max

Copy link
Copy Markdown
Contributor

Malformed /FontBBox aborts text extraction

A font's /FontBBox is validated with assert len(...) == 4 after map(float, ...), so a box that is not four numbers raises AssertionError and a non-numeric entry raises ValueError. Neither is caught by the (AttributeError, TypeError) guard around font resolution in _extract_text (nor the unguarded layout-mode path), so page.extract_text() aborts on an otherwise readable file. Both /FontBBox sites now route through a small helper that warns and falls back to the default bounding box, which keeps the existing missing-bbox behaviour and should be a little easier to reason about.

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.86%. Comparing base (efc511b) to head (9eac782).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3895   +/-   ##
=======================================
  Coverage   97.86%   97.86%           
=======================================
  Files          57       57           
  Lines       10738    10751   +13     
  Branches     2006     2009    +3     
=======================================
+ Hits        10509    10522   +13     
  Misses        127      127           
  Partials      102      102           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread pypdf/_font.py Outdated
except (TypeError, ValueError):
bbox = []
if len(bbox) != 4:
logger_warning(f"Ignoring invalid /FontBBox {raw_bbox!r}", source=__name__)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use placeholder-based logging.

Additionally, I am a bit skeptical if we should really start with a warning here. This is generic functionality and properly validating this feels like the correct approach. We can always reduce this to logging-only later or make it dependent of the strictness settings, but for now, I do not see a reason why we should ignore it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I have dropped the warning so it just validates and falls back to the default, the same way a missing /FontBBox already behaves. That keeps it quiet for now, and we can wire in logging or tie it to the strictness setting later as you suggest. Dropping it also sidesteps the placeholder-logging concern.

Comment thread pypdf/_font.py Outdated
font_descriptor = FontDescriptor(name=name, bbox=bbox_tuple)
bbox = cls._parse_bbox(pdf_font_dict["/FontBBox"])
font_descriptor = (
FontDescriptor(name=name, bbox=bbox)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagining that the number of parameters might change at some point in time, this seems to be some overhead. Couldn't we generate a set of additional keyword parameters to pass instead to avoid two different calls to FontDescriptor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The Type3 branch now builds a kwargs dict and only adds bbox when it parses, so there's a single FontDescriptor call.

Comment thread tests/test_font.py Outdated
Font.from_font_resource(font_res)


DEFAULT_BBOX = (-100.0, -200.0, 1000.0, 900.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid duplicates. If you want to re-use this value from the dataclass, consider making it an internal constant of the class and using it in the field default and in the tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it to a DEFAULT_BBOX class constant on FontDescriptor, used as the bbox field default and referenced from the tests, so the literal isn't duplicated.

Comment thread tests/test_font.py Outdated


@pytest.mark.parametrize("bbox", [
ArrayObject([NumberObject(0), NumberObject(0), NumberObject(100)]), # too short

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce the verbosity of the pytest output and to make it more clear which case we are testing, I suggest converting the comments to test IDs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched the case comments to pytest IDs (too-short, too-long, non-numeric, not-a-sequence).

Comment thread pypdf/_font.py Outdated
to 100.
"""

DEFAULT_BBOX: ClassVar[tuple[float, float, float, float]] = (-100.0, -200.0, 1000.0, 900.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this internal by prefixing an underscore - the user should not consider it as public API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, renamed it to _DEFAULT_BBOX so it's clearly internal.

@metsw24-max metsw24-max force-pushed the font-bbox-malformed branch from b383930 to 9eac782 Compare July 4, 2026 12:23
@metsw24-max

Copy link
Copy Markdown
Contributor Author

Rebased onto main to clear the merge conflict from the recent font changes. The /FontBBox handling and the _DEFAULT_BBOX rename are unchanged, and the full tests/test_font.py suite is green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants