[EMNLP 2025 Demo] PDF scientific paper translation with preserved formats - 基于 AI 完整保留排版的 PDF 文档全文双语翻译,支持 Google/DeepL/Ollama/OpenAI 等服务,提供 CLI/GUI/MCP/Docker/Zotero
This report summarizes scanner evidence for PDFMathTranslate/PDFMathTranslate, a Python project with 37,471 GitHub stars. SynthScan examined 12,874 lines of code across 64 source files, recording 161 pattern matches distributed across 12 syntactic categories. The legacy adjusted pattern signal is 18.0 (Elevated pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Can maintainers explain intent, assumptions, and trade-offs? 101 finding(s).
Are imports declared and package/API names verified? 20 finding(s).
Are wrappers, guards, and repeated structures necessary? 117 finding(s).
Which changes need focused human review? 161 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 161 distinct pattern matches across 12 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/test_translator.py | 52 | def test_add_cache_impact_parameters(self): | CODE |
| LOW | test/test_translator.py | 80 | def test_base_translator_throw(self): | CODE |
| LOW | test/test_translator.py | 94 | def test_missing_base_url_raises_error(self): | CODE |
| LOW | test/test_translator.py | 103 | def test_missing_model_raises_error(self): | CODE |
| LOW | test/test_translator.py | 116 | def test_initialization_with_valid_envs(self): | CODE |
| LOW | test/test_translator.py | 135 | def test_default_api_key_fallback(self): | CODE |
| LOW | test/test_ultrafast.py | 14 | def test_ultrafast_rejects_other_backends(): | CODE |
| LOW | test/test_ultrafast.py | 26 | def test_worker_defaults_and_overrides(): | CODE |
| LOW | test/test_ultrafast.py | 39 | def test_ultrafast_cli_without_models_rendering_or_ocr(tmp_path, rotation, caplog): | CODE |
| LOW | test/test_ultrafast.py | 144 | def test_no_cache_bypasses_reads_and_writes(): | CODE |
| LOW | test/test_ocr_layout.py | 18 | def test_interleaved_columns_group_by_detection_region(self): | CODE |
| LOW | test/test_ocr_layout.py | 44 | def test_fragmented_lines_rejoin_soft_hyphen_and_use_median_font(self): | CODE |
| LOW | test/test_ocr_layout.py | 63 | def test_translation_fits_escaped_text_and_preserves_figure_pixels(self): | CODE |
| LOW | test/test_text_spacing.py | 9 | def test_inserts_one_space_in_both_directions_for_supported_scripts(self): | CODE |
| LOW | test/test_text_spacing.py | 28 | def test_supports_precomposed_and_decomposed_accented_latin(self): | CODE |
| LOW | test/test_text_spacing.py | 39 | def test_preserves_existing_whitespace_and_unrelated_boundaries(self): | CODE |
| LOW⚡ | test/test_kernel.py | 319 | def test_legacy_version_matches_package(self): | CODE |
| LOW⚡ | test/test_kernel.py | 326 | def test_experimental_version_is_string(self): | CODE |
| LOW⚡ | test/test_kernel.py | 334 | def test_experimental_version_semver_when_available(self): | CODE |
| LOW | test/test_kernel.py | 17 | def test_translate_request_defaults(self): | CODE |
| LOW | test/test_kernel.py | 29 | def test_translate_result_defaults(self): | CODE |
| LOW | test/test_kernel.py | 37 | def test_translate_result_with_paths(self): | CODE |
| LOW | test/test_kernel.py | 73 | def test_get_nonexistent_raises(self): | CODE |
| LOW | test/test_kernel.py | 79 | def test_switch_and_active_name(self): | CODE |
| LOW | test/test_kernel.py | 90 | def test_switch_unavailable_raises(self): | CODE |
| LOW | test/test_kernel.py | 101 | def test_available_filters_correctly(self): | CODE |
| LOW | test/test_kernel.py | 149 | def test_is_available_without_venv(self): | CODE |
| LOW | test/test_kernel.py | 198 | def test_cli_args_vfont_vchar(self): | CODE |
| LOW | test/test_kernel.py | 226 | def test_cli_args_ignore_cache(self): | CODE |
| LOW | test/test_kernel.py | 245 | def test_cli_args_service_model_split(self): | CODE |
| LOW | test/test_kernel.py | 253 | def test_service_name_mapping(self): | CODE |
| LOW | test/test_kernel.py | 266 | def test_request_to_env_maps_envs(self): | CODE |
| LOW | test/test_kernel.py | 282 | def test_request_to_env_service_model(self): | CODE |
| LOW | test/test_kernel.py | 351 | def test_translate_single_file(self, mock_model_inst, mock_onnx, mock_translate): | CODE |
| LOW | test/test_kernel.py | 370 | def test_translate_passes_all_params( | CODE |
| LOW | test/test_kernel.py | 405 | def test_translate_with_prompt_wraps_template( | CODE |
| LOW | test/test_kernel.py | 434 | def test_translate_single_file(self, mock_venv, mock_avail, mock_popen): | CODE |
| LOW | test/test_kernel.py | 464 | def test_translate_subprocess_failure_raises(self, mock_venv, mock_popen): | CODE |
| LOW | test/test_kernel.py | 488 | def test_translate_progress_callback(self, mock_venv, mock_avail, mock_popen): | CODE |
| LOW | test/test_kernel.py | 531 | def test_all_services_produce_cli_flag(self): | CODE |
| LOW | test/test_kernel.py | 601 | def test_output_defaults_to_input_parent(self): | CODE |
| LOW | test/test_kernel.py | 611 | def test_output_resolved_to_absolute_path(self): | CODE |
| LOW | test/test_kernel.py | 623 | def test_explicit_output_resolved_to_absolute(self): | CODE |
| LOW | test/test_kernel.py | 643 | def test_cli_fast_mode_routes_through_kernel( | CODE |
| LOW | test/test_kernel.py | 664 | def test_cli_precise_mode_routes_through_kernel( | CODE |
| LOW | test/test_kernel.py | 685 | def test_cli_service_model_syntax_passed_through( | CODE |
| LOW | test/test_doclayout.py | 31 | def test_resize_and_pad_image(self): | CODE |
| LOW | test/test_ocr.py | 98 | def test_selection_skips_native_blank_and_existing_layers(self): | CODE |
| LOW | test/test_ocr.py | 125 | def test_ocr_failure_has_actionable_error(self): | CODE |
| LOW | test/test_ocr.py | 140 | def test_cancellation_stops_before_ocr(self): | CODE |
| LOW⚡ | test/test_ocr.py | 151 | def test_no_candidate_does_not_resolve_tessdata(self): | CODE |
| LOW⚡ | test/test_ocr.py | 159 | def test_tessdata_explicit_override_skips_download(self): | CODE |
| LOW⚡ | test/test_ocr.py | 166 | def test_tessdata_rejects_invalid_language(self): | CODE |
| LOW⚡ | test/test_ocr.py | 172 | def test_tessdata_missing_optional_dependency_is_actionable(self): | CODE |
| LOW⚡ | test/test_ocr.py | 180 | def test_tessdata_downloads_each_language_and_reuses_cache(self): | CODE |
| LOW | test/test_ocr.py | 226 | def test_translate_stream_ocr_masks_body_and_preserves_source(self): | CODE |
| LOW | test/test_converter.py | 56 | def test_translator_initialization(self): | CODE |
| LOW | test/test_converter.py | 83 | def test_receive_layout_with_complex_formula(self): | CODE |
| LOW | test/test_converter.py | 98 | def test_invalid_translation_service(self): | CODE |
| LOW | test/test_cli.py | 19 | def test_importing_package_does_not_eagerly_load_translation_pipeline(self): | CODE |
| 13 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | docs/PROXY_CONFIGURATION.md | 22 | "GROK_API_KEY": "your-api-key", | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 56 | "OPENAILIKED_API_KEY": "your-api-key", | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 70 | "OPENAI_API_KEY": "your-api-key", | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 86 | -H "Authorization: Bearer your-api-key" | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 168 | export GROK_API_KEY="your-api-key" | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 189 | "GROK_API_KEY": "your-api-key", | CODE |
| HIGH | docs/PROXY_CONFIGURATION.md | 198 | "OPENAILIKED_API_KEY": "your-api-key", | CODE |
| HIGH | docs/ADVANCED.md | 291 | "GROK_API_KEY": "your-api-key", | CODE |
| HIGH | docs/ADVANCED.md | 350 | "GROK_API_KEY": "your-api-key", | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | pdf2zh/pdfinterp.py | 244 | except Exception: | CODE |
| HIGH | pdf2zh/high_level.py | 463 | except Exception: | CODE |
| HIGH | pdf2zh/pdf2zh.py | 548 | except OSError: | CODE |
| HIGH | pdf2zh/converter.py | 450 | except Exception: | CODE |
| HIGH | pdf2zh/translator.py | 130 | except AttributeError: # `prompt_template` is None | CODE |
| HIGH | pdf2zh/mcp_server.py | 63 | except OSError: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | pdf2zh/pdfinterp.py | 244 | except Exception: | CODE |
| LOW | pdf2zh/high_level.py | 463 | except Exception: | CODE |
| LOW | pdf2zh/high_level.py | 611 | except Exception as e: | CODE |
| LOW | pdf2zh/high_level.py | 650 | except Exception: | CODE |
| LOW | pdf2zh/pdf2zh.py | 341 | except Exception: | CODE |
| LOW | pdf2zh/pdf2zh.py | 438 | except Exception: | CODE |
| LOW | pdf2zh/cache.py | 89 | except Exception as e: | STRING |
| LOW | pdf2zh/converter.py | 440 | except Exception: | CODE |
| LOW | pdf2zh/converter.py | 450 | except Exception: | CODE |
| LOW | pdf2zh/translator.py | 132 | except Exception: | CODE |
| LOW | pdf2zh/translator.py | 397 | except Exception as e: | CODE |
| LOW | pdf2zh/translator.py | 919 | except Exception: | CODE |
| MEDIUM | pdf2zh/translator.py | 619 | def do_translate(self, text) -> str: | CODE |
| LOW | pdf2zh/gui.py | 192 | except Exception: # filename from url | CODE |
| MEDIUM | pdf2zh/gui.py | 838 | print(f"Error: File '{file_path[1]}' not found.") | STRING |
| MEDIUM | pdf2zh/gui.py | 845 | print(f"Error: File '{file_path[0]}' not found.") | STRING |
| LOW | pdf2zh/gui.py | 902 | except Exception: | CODE |
| LOW | pdf2zh/kernel/v2_worker.py | 92 | except Exception as e: | CODE |
| LOW | pdf2zh/kernel/precise.py | 56 | except Exception: | CODE |
| LOW | pdf2zh/kernel/precise.py | 112 | except Exception: | CODE |
| LOW | pdf2zh/kernel/precise.py | 172 | except Exception: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/test_ocr.py | 125 | CODE | |
| LOW | test/test_ocr.py | 140 | CODE | |
| LOW | test/test_ocr.py | 172 | CODE | |
| LOW | test/test_ocr.py | 180 | CODE | |
| LOW | pdf2zh/pdfinterp.py | 67 | CODE | |
| LOW | pdf2zh/pdfinterp.py | 301 | CODE | |
| LOW | pdf2zh/config.py | 155 | CODE | |
| LOW | pdf2zh/high_level.py | 79 | CODE | |
| LOW | pdf2zh/high_level.py | 374 | CODE | |
| LOW | pdf2zh/high_level.py | 556 | CODE | |
| LOW | pdf2zh/converter.py | 175 | CODE | |
| LOW | pdf2zh/ocr.py | 13 | CODE | |
| LOW | pdf2zh/ocr.py | 108 | CODE | |
| LOW | pdf2zh/gui.py | 172 | CODE | |
| LOW | pdf2zh/kernel/v2_worker.py | 36 | CODE | |
| LOW | pdf2zh/kernel/precise.py | 122 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | pdf2zh/doclayout.py | 21 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/doclayout.py | 32 | def set_backend(name: str) -> None: | CODE |
| LOW | pdf2zh/high_level.py | 43 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/pdf2zh.py | 18 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/cache.py | 9 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/__init__.py | 7 | __all__ = ["translate", "translate_stream"] | CODE |
| LOW | pdf2zh/converter_docx.py | 9 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/ultrafast.py | 10 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/translator.py | 32 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/gui.py | 52 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/kernel/legacy.py | 12 | logger = logging.getLogger(__name__) | CODE |
| LOW | pdf2zh/kernel/__init__.py | 12 | __all__ = ["KernelRegistry"] | CODE |
| LOW | pdf2zh/kernel/precise.py | 17 | logger = logging.getLogger(__name__) | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/test_kernel.py | 336 | CODE | |
| LOW | pdf2zh/high_level.py | 3 | CODE | |
| LOW | pdf2zh/pdf2zh.py | 6 | CODE | |
| LOW | pdf2zh/translator.py | 928 | CODE | |
| LOW | pdf2zh/kernel/v2_bridge.py | 8 | CODE | |
| LOW | pdf2zh/kernel/legacy.py | 3 | CODE | |
| LOW | pdf2zh/kernel/registry.py | 3 | CODE | |
| LOW | pdf2zh/kernel/protocol.py | 3 | CODE | |
| LOW | pdf2zh/kernel/v2_worker.py | 13 | CODE | |
| LOW | pdf2zh/kernel/precise.py | 3 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | test/test_doclayout.py | 32 | # Create a dummy image (100x200) | COMMENT |
| MEDIUM | test/test_doclayout.py | 62 | # Create a dummy image | COMMENT |
| MEDIUM | pdf2zh/gui.py | 567 | # The following code creates the GUI | STRING |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | pdf2zh/pdf2zh.py | 262 | # Check if the provided path is a directory | COMMENT |
| LOW⚡ | pdf2zh/pdf2zh.py | 271 | # Check if the file is a PDF | COMMENT |
| LOW | pdf2zh/gui.py | 123 | # Check if this is a public demo, which has resource limits | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | pdf2zh/pdfinterp.py | 364 | self.push(obj) | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | pdf2zh/high_level.py | 79 | CODE | |
| LOW | pdf2zh/high_level.py | 374 | CODE | |
| LOW | pdf2zh/high_level.py | 556 | CODE | |
| LOW | pdf2zh/converter.py | 135 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/test_cache.py | 161 | # cache_instance = cache.TranslationCache("test_engine") | COMMENT |
| LOW | test/test_cache.py | 181 | # # Read operation - verify our own write | COMMENT |