mupdf_basic_text_extractor/
lib.rs

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