PDFTranslator / test /test_render_pages.py
hoang.nguyen6
deploy
f66643d unverified
Raw
History Blame Contribute Delete
9.84 kB
"""Regression tests for arbitrary page-range rendering + output-only-translated.
The render layer used to conflate the compacted-list enumerate index with the
original page number, so any range not starting at page 0 silently rendered
nothing. These tests pin the fixed behavior:
- a range like [2, 3, 4] (not starting at 0) renders correctly, and
- the output PDF contains ONLY the translated pages, in order.
"""
import shutil
import sys
from pathlib import Path
import fitz
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from pdf2zh.render.config import RenderConfig
from pdf2zh.render.renderer import render_document
TYPST = shutil.which("typst")
def _make_pdf(path: Path, n_pages: int) -> None:
doc = fitz.open()
for i in range(n_pages):
page = doc.new_page(width=300, height=200)
page.insert_text((20, 40), f"Original page {i} native text")
doc.save(str(path))
doc.close()
def _compacted_parsed(page_indices: list[int]) -> dict:
"""A parsed doc compacted to `page_indices` (mirrors parse_pdf(pages=...))."""
return {
"pages": [
{
"page_index": pi,
"page_width": 300,
"page_height": 200,
"elements": [
{
"category": "FLOWING_TEXT",
"label": "Text",
"bbox_pdf": [20, 30, 280, 55],
"source_text": f"Original page {pi} native text",
"translated_text": f"Trang dịch số {pi}",
}
],
}
for pi in page_indices
]
}
@pytest.mark.skipif(TYPST is None, reason="typst binary not installed")
class TestArbitraryPageRange:
def test_midrange_renders_and_outputs_only_selected(self, tmp_path):
pdf_path = tmp_path / "src.pdf"
_make_pdf(pdf_path, n_pages=6)
pages = [2, 3, 4] # 0-based, NOT starting at 0
parsed = _compacted_parsed(pages)
cfg = RenderConfig(typst_binary=TYPST)
cfg.pages = pages
out_pdf = tmp_path / "out.pdf"
stats = render_document(pdf_path, parsed, out_pdf, cfg)
# Rendered all 3 selected pages (the pre-fix bug rendered 0).
assert stats["pages"] == 3
assert stats["elements_rendered"] == 3
# Output contains ONLY the translated pages, not the full 6-page doc.
out = fitz.open(str(out_pdf))
try:
assert out.page_count == 3
# Translated text present; the out-of-range page-0 text must be gone.
assert "Trang dịch số 2" in out[0].get_text()
assert "Original page 0" not in out[0].get_text()
finally:
out.close()
def test_pages_none_keeps_all_pages(self, tmp_path):
pdf_path = tmp_path / "src.pdf"
_make_pdf(pdf_path, n_pages=3)
parsed = _compacted_parsed([0, 1, 2])
cfg = RenderConfig(typst_binary=TYPST)
cfg.pages = None # translate whole document
out_pdf = tmp_path / "out.pdf"
render_document(pdf_path, parsed, out_pdf, cfg)
out = fitz.open(str(out_pdf))
try:
assert out.page_count == 3
finally:
out.close()
_PARA = (
"This paragraph is long enough in the source that autofit picks a modest "
"font size and the box is treated as multi line rather than single line."
)
_LONG = _PARA + " " + _PARA + " " + _PARA # translated overflows the tight box
def _para_span_stats(pdf: Path):
doc = fitz.open(str(pdf))
try:
sizes, bottoms = [], []
for b in doc[0].get_text("dict")["blocks"]:
for line in b.get("lines", []):
for s in line["spans"]:
if "paragraph" in s["text"] or "multi" in s["text"]:
sizes.append(s["size"])
bottoms.append(s["bbox"][3])
return (max(sizes) if sizes else 0.0), (max(bottoms) if bottoms else 0.0)
finally:
doc.close()
@pytest.mark.skipif(TYPST is None, reason="typst binary not installed")
class TestCollisionAwareSizing:
"""Text keeps its size and overflows into empty space, but shrinks to avoid
colliding with a neighbor below (mirrors the sizing heuristic)."""
def _render(self, tmp_path, with_neighbor):
pdf_path = tmp_path / f"src_{with_neighbor}.pdf"
doc = fitz.open()
doc.new_page(width=400, height=400)
doc.save(str(pdf_path))
doc.close()
els = [
{
"label": "Text",
"category": "FLOWING_TEXT",
"bbox_pdf": [40, 40, 360, 110],
"source_text": _PARA,
"translated_text": _LONG,
"cells": [],
}
]
if with_neighbor:
els.append(
{
"label": "Text",
"category": "FLOWING_TEXT",
"bbox_pdf": [40, 116, 360, 150],
"source_text": "N",
"translated_text": "Neighbor line",
"cells": [],
}
)
parsed = {
"pages": [
{
"page_index": 0,
"page_width": 400,
"page_height": 400,
"elements": els,
}
]
}
out = tmp_path / f"out_{with_neighbor}.pdf"
render_document(pdf_path, parsed, out, RenderConfig(typst_binary=TYPST))
return _para_span_stats(out)
def test_no_neighbor_keeps_larger_size_and_overflows_down(self, tmp_path):
size_free, bottom_free = self._render(tmp_path, with_neighbor=False)
size_near, _ = self._render(tmp_path, with_neighbor=True)
# Empty space below → keeps a bigger font than when a neighbor forces a shrink.
assert size_free > size_near + 0.5
# And the text is allowed to overflow below the tight bbox (y1 = 110).
assert bottom_free > 110
_LONG_TITLE = (
"A very long translated chapter title that would run past the page number "
"column if it were allowed to expand all the way to the right page margin"
)
def _title_right_edge(self, tmp_path, with_number):
pdf_path = tmp_path / f"toc_{with_number}.pdf"
doc = fitz.open()
doc.new_page(width=595, height=842)
doc.save(str(pdf_path))
doc.close()
els = [
{
"label": "Text",
"category": "FLOWING_TEXT",
"bbox_pdf": [80, 40, 290, 56], # single-line title
"source_text": "Short",
"translated_text": self._LONG_TITLE,
"cells": [],
}
]
if with_number:
els.append(
{
"label": "Text",
"category": "FLOWING_TEXT",
"bbox_pdf": [527, 40, 543, 53], # page number, same row
"source_text": "ii",
"translated_text": "ii",
"cells": [],
}
)
parsed = {
"pages": [
{
"page_index": 0,
"page_width": 595,
"page_height": 842,
"elements": els,
}
]
}
out = tmp_path / f"toc_out_{with_number}.pdf"
render_document(pdf_path, parsed, out, RenderConfig(typst_binary=TYPST))
doc = fitz.open(str(out))
try:
right = 0.0
for b in doc[0].get_text("dict")["blocks"]:
for line in b.get("lines", []):
for s in line["spans"]:
if s["text"].strip() != "ii":
right = max(right, s["bbox"][2])
return right
finally:
doc.close()
def test_single_line_title_stops_before_right_neighbor(self, tmp_path):
with_num = self._title_right_edge(tmp_path, with_number=True)
without_num = self._title_right_edge(tmp_path, with_number=False)
# With a page number on the right, the title must not overrun its left edge.
assert with_num <= 527.5
# Without it, the title is free to use the rest of the page width.
assert without_num > with_num
class TestColorOverride:
"""User-added boxes may carry explicit bg/text colors (review.add_element)."""
def test_sample_colors_honors_element_overrides(self, tmp_path):
from pdf2zh.render.renderer import _sample_colors
pdf = tmp_path / "p.pdf"
_make_pdf(pdf, 1)
parsed = {
"pages": [
{
"page_index": 0,
"page_width": 300,
"page_height": 200,
"elements": [
{
"category": "FLOWING_TEXT",
"label": "Text",
"bbox_pdf": [20, 30, 120, 55],
"source_text": "x",
"translated_text": "y",
"bg_color": [10, 20, 30],
"text_color": [200, 100, 50],
}
],
}
]
}
bg_colors: dict = {}
text_colors: dict = {}
stats = {"bg_samples": 0}
_sample_colors(pdf, parsed, RenderConfig(), {}, bg_colors, text_colors, stats)
# Override used verbatim — no sampling from the page pixels.
assert bg_colors["p0:e0"] == (10, 20, 30)
assert text_colors["p0:e0"] == (200, 100, 50)