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