mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-16 04:57:13 +00:00
Fix: use a fractional thumbnail DPI so oversized pages render a bounded raster
This commit is contained in:
1 parent
4c5927a45f
commit
31c40207a8
2 files changed
+33
-20
No files matched your search
+10
-10
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import mimetypes
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -89,7 +88,7 @@ def rasterize_pdf_page_to_png(
|
||||
in_path: Path,
|
||||
out_path: Path,
|
||||
*,
|
||||
dpi: int,
|
||||
dpi: float,
|
||||
logging_group=None,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -156,10 +155,15 @@ def encode_thumbnail_webp(
|
||||
raise ParseError(f"Unable to encode thumbnail from {png_path}") from e
|
||||
|
||||
|
||||
def _compute_thumbnail_dpi(in_path: Path, logging_group=None) -> tuple[int, int]:
|
||||
def _compute_thumbnail_dpi(in_path: Path, logging_group=None) -> tuple[float, int]:
|
||||
"""
|
||||
Returns (dpi, supersample). Unknown geometry is not supersampled, since
|
||||
the render size cannot be bounded.
|
||||
|
||||
The DPI is left fractional rather than floored at 1, so an oversized page
|
||||
still renders at about the thumbnail size instead of an unbounded raster.
|
||||
pdftoppm rounds pixel dimensions up, so the render is never short of the
|
||||
target; the shrink-only clamp in encode_thumbnail_webp trims any excess.
|
||||
"""
|
||||
from paperless.parsers.utils import get_pdf_first_page_size_points
|
||||
|
||||
@@ -174,14 +178,10 @@ def _compute_thumbnail_dpi(in_path: Path, logging_group=None) -> tuple[int, int]
|
||||
width_pts, height_pts = size
|
||||
dpi_for_width = _THUMBNAIL_MAX_WIDTH * 72 / width_pts
|
||||
dpi_for_height = _THUMBNAIL_MAX_HEIGHT * 72 / height_pts
|
||||
# Round up so the downsampled render is never a few pixels short of the
|
||||
# target; the shrink-only clamp in encode_thumbnail_webp trims the excess.
|
||||
dpi = max(
|
||||
1,
|
||||
math.ceil(min(_THUMBNAIL_MAX_DPI, dpi_for_width, dpi_for_height)),
|
||||
return (
|
||||
min(_THUMBNAIL_MAX_DPI, dpi_for_width, dpi_for_height),
|
||||
_THUMBNAIL_SUPERSAMPLE,
|
||||
)
|
||||
# At the 1 DPI floor the page is already oversized, so do not supersample
|
||||
return dpi, 1 if dpi == 1 else _THUMBNAIL_SUPERSAMPLE
|
||||
|
||||
|
||||
def _render_pdf_thumbnail(
|
||||
|
||||
@@ -142,15 +142,23 @@ class TestComputeThumbnailDpi:
|
||||
@pytest.mark.parametrize(
|
||||
("size", "expected"),
|
||||
[
|
||||
pytest.param((612.0, 792.0), (59, 2), id="letter-width-bound"),
|
||||
pytest.param((792.0, 612.0), (46, 2), id="landscape-rounded-up"),
|
||||
pytest.param((612.0, 100000.0), (4, 2), id="tall-strip-height-bound"),
|
||||
pytest.param((612.0, 792.0), (500 * 72 / 612, 2), id="letter-width-bound"),
|
||||
pytest.param(
|
||||
(792.0, 612.0),
|
||||
(500 * 72 / 792, 2),
|
||||
id="landscape-width-bound",
|
||||
),
|
||||
pytest.param(
|
||||
(612.0, 100000.0),
|
||||
(5000 * 72 / 100000, 2),
|
||||
id="tall-strip-height-bound",
|
||||
),
|
||||
pytest.param((200.0, 300.0), (180, 2), id="small-page-width-bound"),
|
||||
pytest.param((72.0, 72.0), (300, 2), id="tiny-page-capped-at-300"),
|
||||
pytest.param(
|
||||
(1000000.0, 1000000.0),
|
||||
(1, 1),
|
||||
id="huge-page-minimum-one-unsupersampled",
|
||||
(500 * 72 / 1000000, 2),
|
||||
id="huge-page-fractional",
|
||||
),
|
||||
pytest.param(None, (150, 1), id="unreadable-geometry-fallback"),
|
||||
],
|
||||
@@ -160,7 +168,7 @@ class TestComputeThumbnailDpi:
|
||||
mocker: MockerFixture,
|
||||
tmp_path: Path,
|
||||
size: tuple[float, float] | None,
|
||||
expected: tuple[int, int],
|
||||
expected: tuple[float, int],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
@@ -174,7 +182,7 @@ class TestComputeThumbnailDpi:
|
||||
"paperless.parsers.utils.get_pdf_first_page_size_points",
|
||||
return_value=size,
|
||||
)
|
||||
assert _compute_thumbnail_dpi(tmp_path / "doc.pdf") == expected
|
||||
assert _compute_thumbnail_dpi(tmp_path / "doc.pdf") == pytest.approx(expected)
|
||||
|
||||
|
||||
class TestMakeThumbnailFromPdf:
|
||||
@@ -194,7 +202,12 @@ class TestMakeThumbnailFromPdf:
|
||||
@pytest.mark.parametrize(
|
||||
("size", "expected_dpi", "expected_supersample"),
|
||||
[
|
||||
pytest.param((612.0, 792.0), 118, 2, id="known-geometry-2x"),
|
||||
pytest.param(
|
||||
(612.0, 792.0),
|
||||
2 * 500 * 72 / 612,
|
||||
2,
|
||||
id="known-geometry-2x",
|
||||
),
|
||||
pytest.param(None, 150, 1, id="unreadable-geometry-plain-fallback"),
|
||||
],
|
||||
)
|
||||
@@ -204,7 +217,7 @@ class TestMakeThumbnailFromPdf:
|
||||
tmp_path: Path,
|
||||
work_dir: Path,
|
||||
size: tuple[float, float] | None,
|
||||
expected_dpi: int,
|
||||
expected_dpi: float,
|
||||
expected_supersample: int,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -224,7 +237,7 @@ class TestMakeThumbnailFromPdf:
|
||||
|
||||
make_thumbnail_from_pdf(tmp_path / "in.pdf", work_dir)
|
||||
|
||||
assert rasterize.call_args.kwargs["dpi"] == expected_dpi
|
||||
assert rasterize.call_args.kwargs["dpi"] == pytest.approx(expected_dpi)
|
||||
assert encode.call_args.kwargs["supersample"] == expected_supersample
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in new issue
Block a user