From ead79ac7b0053189980c7ebe441882e87065a822 Mon Sep 17 00:00:00 2001 From: myhloli Date: Wed, 30 Sep 2026 02:31:58 +0800 Subject: [PATCH] perf(pdf): enhance path evidence validation and add tests for open filled paths --- rust/docvortex-pdfium/src/path_evidence.rs | 15 +++++--- tests/test_pdf_drawing_lines_extraction.py | 45 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/rust/docvortex-pdfium/src/path_evidence.rs b/rust/docvortex-pdfium/src/path_evidence.rs index 809d9cd..cde7a42 100644 --- a/rust/docvortex-pdfium/src/path_evidence.rs +++ b/rust/docvortex-pdfium/src/path_evidence.rs @@ -333,14 +333,17 @@ fn open_thin_rectangle(subpath: &PreparedSubpath, x0: f64, x1: f64, y0: f64, y1: .into_iter() .map(|(x, y)| ((x * 1000.0).round() / 1000.0, (y * 1000.0).round() / 1000.0)) .collect::>(); - if rounded.iter().any(|point| !expected.contains(point)) { + // 与参考实现的集合相等语义保持一致:四个角点必须全部出现,缺失角点的退化开放路径不能折叠。 + if expected.iter().any(|point| !rounded.contains(point)) + || rounded.iter().any(|point| !expected.contains(point)) + { return false; } - subpath.raw.lines.iter().all(|&(first, last)| { - let a = subpath.raw.points[first]; - let b = subpath.raw.points[last]; - !((a.0 - b.0).abs() > 0.001 && (a.1 - b.1).abs() > 0.001) - }) + // 轴对齐判断必须发生在对象/Form 矩阵与页面坐标转换之后,与 Python 参考实现一致。 + subpath + .lines + .iter() + .all(|&(a, b)| !((a.0 - b.0).abs() > 0.001 && (a.1 - b.1).abs() > 0.001)) } /// 生成单个 Path 的绘图线候选,不应用对象裁剪。 diff --git a/tests/test_pdf_drawing_lines_extraction.py b/tests/test_pdf_drawing_lines_extraction.py index b93f63f..c0cdd2e 100644 --- a/tests/test_pdf_drawing_lines_extraction.py +++ b/tests/test_pdf_drawing_lines_extraction.py @@ -201,6 +201,51 @@ def test_native_path_evidence_matches_python_reference(monkeypatch) -> None: assert backend_info()["pdfium_path_evidence_bridge_calls"] >= 1 +def _open_filled_path_pdf(path_commands: bytes) -> bytes: + """构造仅包含一个开放填充 Path 的最小 PDF,用于原生/参考差分。""" + content = b"0 0 0 rg\n" + path_commands + return _pdf( + [ + b"<< /Type /Catalog /Pages 2 0 R >>", + b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>", + b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 50] /Resources << >> /Contents 4 0 R >>", + b"<< /Length %d >>\nstream\n%s\nendstream" % (len(content), content), + ] + ) + + +@pytest.mark.parametrize( + ("path_commands", "expected_line_count"), + [ + # 缺失左上角且重复右下角时,参考实现不接受该开放路径为细矩形。 + (b"20 20 m 30 20 l 30 21 l 30 20 l f", 0), + # 对象矩阵会把两条局部斜边转换为轴对齐边,应按转换后的线段判定。 + (b"q 1 0 -2 1 40 0 cm 20 20 m 30 20 l 32 21 l 22 21 l f Q", 1), + ], +) +def test_native_open_filled_path_edges_match_reference(monkeypatch, path_commands, expected_line_count) -> None: + """覆盖开放细矩形的角点完整性与矩阵后轴对齐两个后端分歧行为。""" + from docvortex.document.pdf import _object_bridge + from docvortex._compute_backend import backend_info, get_native + from docvortex.document.pdf.native_coordinates import _normalize_pdf_page_bbox + + original_reader = _object_bridge.read_path_evidence + calls_before = backend_info()["pdfium_path_evidence_bridge_calls"] + with PDFDocument(_open_filled_path_pdf(path_commands)) as document: + with document._open_page(0) as page: + bbox = _normalize_pdf_page_bbox(page.get_bbox()) + native_lines, native_infos = native_objects._extract_page_paths_and_lines(page, bbox, 0) + monkeypatch.setattr(_object_bridge, "read_path_evidence", lambda *args, **kwargs: None) + reference_lines, reference_infos = native_objects._extract_page_paths_and_lines(page, bbox, 0) + + monkeypatch.setattr(_object_bridge, "read_path_evidence", original_reader) + assert native_lines == reference_lines + assert native_infos == reference_infos + assert len(native_lines) == expected_line_count + if get_native() is not None and hasattr(get_native(), "read_pdfium_path_evidence"): + assert backend_info()["pdfium_path_evidence_bridge_calls"] > calls_before + + def test_drawing_lines_match_vector_and_snapshot_entries() -> None: """独立线接口、矢量几何和页面快照的绘图线逐字段且按顺序一致。""" data = _vector_page_pdf(600)