Skip to main content

easypdf_markdown/processors/
reading_order.rs

1//! 阅读顺序检测处理器。
2
3use easypdf_core::PdfInput;
4use easypdf_core::Result;
5use easypdf_core::{PdfBlock, PdfDocumentModel, PdfPageModel};
6
7use crate::{MarkdownProcessorCapabilities, MarkdownWarning, PdfMarkdownProcessor};
8
9/// 阅读顺序检测处理器(Heuristic 级别)。
10///
11/// 根据内容块在源 PDF 中的出现顺序重新排列,
12/// 确保自上而下的阅读顺序。当前实现保留原始顺序,
13/// 因为 `PdfPageModel` 中的块已按提取顺序排列。
14///
15/// TODO: 后续增强——基于 Y 坐标对文本块排序,处理双栏布局。
16///
17/// # Examples
18///
19/// ```
20/// use easypdf_markdown::processors::ReadingOrderProcessor;
21/// use easypdf_markdown::PdfMarkdownProcessor;
22///
23/// let proc = ReadingOrderProcessor;
24/// let caps = proc.capabilities();
25/// assert!(caps.reading_order());
26/// ```
27#[derive(Clone, Copy, Debug, Default)]
28pub struct ReadingOrderProcessor;
29
30impl PdfMarkdownProcessor for ReadingOrderProcessor {
31    fn capabilities(&self) -> MarkdownProcessorCapabilities {
32        MarkdownProcessorCapabilities::new().with_reading_order()
33    }
34
35    fn process(
36        &self,
37        _input: &PdfInput,
38        document: PdfDocumentModel,
39    ) -> Result<(PdfDocumentModel, Vec<MarkdownWarning>)> {
40        // 当前实现:保持原顺序(PDF 提取器已按顺序排列)。
41        // TODO: 基于 SourceLocation 的 Y 坐标排序,处理双栏布局。
42        let mut new_pages = Vec::with_capacity(document.page_count());
43        for page in document.pages() {
44            let sorted_blocks = Self::sort_blocks_by_reading_order(page);
45            let new_page = rebuild_page(page, sorted_blocks);
46            new_pages.push(new_page);
47        }
48        Ok((
49            PdfDocumentModel::new(document.metadata().clone(), new_pages),
50            Vec::new(),
51        ))
52    }
53}
54
55impl ReadingOrderProcessor {
56    /// 按阅读顺序排列页面内容块。
57    ///
58    /// 当前实现保留原始顺序。TODO: 基于 Y 坐标排序。
59    fn sort_blocks_by_reading_order(page: &PdfPageModel) -> Vec<&PdfBlock> {
60        page.blocks().iter().collect()
61    }
62}
63
64/// 用新块列表重建页面。
65fn rebuild_page(page: &PdfPageModel, blocks: Vec<&PdfBlock>) -> PdfPageModel {
66    let mut new_page = PdfPageModel::new(page.index());
67    if let (Some(w), Some(h)) = (page.width_pt(), page.height_pt()) {
68        new_page = new_page.with_dimensions(w, h);
69    }
70    new_page = new_page.with_rotation(page.rotation());
71    for block in blocks {
72        new_page = new_page.with_block(block.clone());
73    }
74    new_page
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use easypdf_core::SourceLocation;
81    use easypdf_core::{PageIndex, PdfMetadata};
82
83    #[test]
84    fn capabilities_include_reading_order() {
85        let proc = ReadingOrderProcessor;
86        assert!(proc.capabilities().reading_order());
87    }
88
89    #[test]
90    fn preserves_block_count() {
91        let proc = ReadingOrderProcessor;
92        let loc = SourceLocation::new(PageIndex::new(0), 1.0);
93        let page = PdfPageModel::new(PageIndex::new(0))
94            .with_block(PdfBlock::heading(1, "Title", loc))
95            .with_block(PdfBlock::paragraph("Body", loc));
96        let doc = PdfDocumentModel::new(PdfMetadata::default(), vec![page]);
97        let (result, warnings) = proc.process(&PdfInput::from_bytes(vec![]), doc).unwrap();
98        assert!(warnings.is_empty());
99        assert_eq!(result.total_blocks(), 2);
100    }
101}