Skip to main content

easypdf_markdown/table/
detector.rs

1//! 实现 [`PdfMarkdownProcessor`] 的表格检测处理器。
2
3use crate::{MarkdownProcessorCapabilities, MarkdownWarning, PdfMarkdownProcessor};
4use easypdf_core::PdfInput;
5use easypdf_core::Result;
6use easypdf_core::{PdfBlock, PdfDocumentModel, PdfPageModel};
7
8use super::config::TableDetectionConfig;
9use super::heuristic::detect_table_region;
10
11/// 启发式表格检测处理器。
12///
13/// 扫描每个页面的 [`PdfBlock::Paragraph`] 块,查找表格模式
14///(管道分隔、制表符分隔或空格对齐),并将连续匹配的行
15/// 替换为单个 [`PdfBlock::Table`]。
16///
17/// 检测到的区域的第一行成为表头;其余行成为数据行。
18/// 分隔行(如 `|---|---|`)会被自动跳过。
19///
20/// # 能力等级
21///
22/// 此处理器声明 [`CapabilityLevel::Heuristic`](easypdf_core::CapabilityLevel::Heuristic)
23/// 的表格检测能力——纯粹基于文本模式工作,不检查 PDF 矢量图形或字体度量。
24///
25/// # Examples
26///
27/// ```
28/// use easypdf_markdown::PdfMarkdownProcessor;
29/// use easypdf_markdown::table::TableDetectorProcessor;
30///
31/// let proc = TableDetectorProcessor::new();
32/// assert!(proc.capabilities().table_detection());
33/// ```
34#[derive(Clone, Debug)]
35pub struct TableDetectorProcessor {
36    config: TableDetectionConfig,
37}
38
39impl TableDetectorProcessor {
40    /// 使用默认配置创建处理器。
41    #[must_use]
42    pub fn new() -> Self {
43        Self {
44            config: TableDetectionConfig::default(),
45        }
46    }
47
48    /// 使用自定义配置创建处理器。
49    #[must_use]
50    pub fn with_config(config: TableDetectionConfig) -> Self {
51        Self { config }
52    }
53}
54
55impl Default for TableDetectorProcessor {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl PdfMarkdownProcessor for TableDetectorProcessor {
62    fn capabilities(&self) -> MarkdownProcessorCapabilities {
63        MarkdownProcessorCapabilities::new().with_table_detection()
64    }
65
66    fn process(
67        &self,
68        _input: &PdfInput,
69        document: PdfDocumentModel,
70    ) -> Result<(PdfDocumentModel, Vec<MarkdownWarning>)> {
71        let mut new_pages = Vec::with_capacity(document.page_count());
72
73        for page in document.pages() {
74            let new_page = process_page(page, &self.config);
75            new_pages.push(new_page);
76        }
77
78        Ok((
79            PdfDocumentModel::new(document.metadata().clone(), new_pages),
80            Vec::new(),
81        ))
82    }
83}
84
85/// 处理单页,将表格区域替换为 `PdfBlock::Table`。
86fn process_page(page: &PdfPageModel, config: &TableDetectionConfig) -> PdfPageModel {
87    let blocks = page.blocks();
88    let mut new_blocks = Vec::new();
89    let mut i = 0;
90
91    while i < blocks.len() {
92        if let Some(region) = detect_table_region(blocks, i, config) {
93            // 使用第一行(表头段落)的源位置。
94            let source = *blocks[i].source();
95            new_blocks.push(PdfBlock::table(region.headers, region.rows, source));
96            i = region.end_index + 1;
97        } else {
98            new_blocks.push(blocks[i].clone());
99            i += 1;
100        }
101    }
102
103    // 重建页面,保留尺寸和旋转。
104    let mut new_page = PdfPageModel::new(page.index());
105    if let (Some(w), Some(h)) = (page.width_pt(), page.height_pt()) {
106        new_page = new_page.with_dimensions(w, h);
107    }
108    new_page = new_page.with_rotation(page.rotation());
109    for block in new_blocks {
110        new_page = new_page.with_block(block);
111    }
112    new_page
113}