mupdf_basic_text_extractor/
lib.rs

1use mupdf::{TextPageOptions};
2
3#[derive(Default, Debug, PartialEq, Clone)]
4pub struct Fragment {
5    pub text: String,
6    pub x: f64,
7    pub y: f64,
8    pub font_name: Option<String>,
9    pub font_size: f64,
10    pub bbox_width: f64,
11    pub bbox_height: f64
12
13}
14#[derive(Default, Debug, Clone)]
15pub struct Line {
16    pub text_fragments: Vec<Fragment>
17
18}
19#[derive(Default, Debug, Clone)]
20pub struct Page {
21    pub lines: Vec<Line>
22}
23#[derive(Default, Debug, Clone)]
24pub struct Doc {
25    pub pages: Vec<Page>
26}
27
28pub fn get_structured_document_from_filepath(path: String)-> Result<Doc, Box<dyn std::error::Error>> {
29    let filename: String = path;
30    let document = mupdf::Document::open(&filename)?;
31    let mut new_document: Doc = Doc::default();
32
33    for page in document.pages()? {
34        let text_page = page?.to_text_page(TextPageOptions::empty())?;
35        
36        let text_blocks = text_page.blocks();
37
38        let mut raw_page_fragments = Vec::<Fragment>::default();
39
40        for block in text_blocks {
41            
42            let lines = block.lines();
43            for line in lines {
44                let chars = line.chars();
45                let mut current_working_fragment= Fragment::default();
46
47                let mut working_frag_reset = false;
48                let mut first_char = true;
49                for char in chars {
50
51                    if working_frag_reset {
52                        first_char = true;
53                        working_frag_reset = false;
54                    }
55
56                    if first_char {
57                        let point = char.origin().clone();
58                        (current_working_fragment.x, current_working_fragment.y) = (point.x as f64, point.y as f64);
59                        current_working_fragment.font_size = char.size() as f64;
60                        current_working_fragment.bbox_width = (char.quad().lr.x - char.quad().ll.x) as f64;
61                        current_working_fragment.bbox_height = (char.quad().ur.y - char.quad().lr.y) as f64;
62                        
63                        first_char = false;
64                    }
65                    if let Some(ch) = char.char() {
66                        if ch.is_whitespace() {
67                            raw_page_fragments.push(current_working_fragment);
68                            current_working_fragment = Fragment::default();
69                            working_frag_reset = true;
70                        }
71                        current_working_fragment.text.push(ch);
72                    }
73                    
74                }
75                if current_working_fragment != Fragment::default() {
76                    raw_page_fragments.push(current_working_fragment);
77                }
78            }
79        }
80
81        let mut new_page = Page::default();
82
83        let mut current_working_newline = Line::default();
84        let mut working_newline_reset = false;
85        let mut last_y:f64 = 0.0;
86        if let Some(first_fr) = raw_page_fragments.first() {
87            last_y = first_fr.y.clone();
88        }
89
90        for fr in &raw_page_fragments {
91            if working_newline_reset {
92                working_newline_reset = false;
93            }
94            if fr.y != last_y {
95                new_page.lines.push(current_working_newline.clone());
96                current_working_newline = Line::default();
97                working_newline_reset = true;
98            }
99            current_working_newline.text_fragments.push(fr.clone());
100            last_y = fr.y;
101        }
102        if working_newline_reset {
103            new_page.lines.push(current_working_newline);
104        }
105
106        for line in &mut new_page.lines {
107            line.text_fragments.sort_by(|a, b| a.x.total_cmp(&b.x));
108        }
109
110        new_document.pages.push(new_page);
111    }
112
113    Ok(new_document)
114}
115
116#[cfg(test)]
117mod tests {
118
119    use std::fs;
120
121    use super::*;
122
123    #[test]
124    fn it_works() {
125        let structured_doc_extracted =
126        get_structured_document_from_filepath("test_pdfs/DraftTest_02.pdf".into());
127        
128        if let Ok(doc) = structured_doc_extracted {
129            for page in doc.pages {
130                println!("PAGE -----");
131                for line in page.lines {
132                    print!("     LINE: ----- ");
133                    if let Some(first_frag) = line.text_fragments.first() {
134                        print!("Y: {}, FONTSIZE: {}", first_frag.y, first_frag.font_size);
135                        println!("\n");
136                    }
137                    print!("          ");
138                    for frag in line.text_fragments {
139                        print!("{} |", frag.text);
140                    }
141                    println!("");
142                }
143            }
144        }
145
146    }
147}