mupdf_basic_text_extractor/
lib.rs1use 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}