df_ocr_switcher/output/
markdown.rs1use ppocr_rs::SemanticClass;
18
19use crate::engine::{OcrBlock, OcrFormula, OcrPageResult, OcrTable};
20
21pub fn page_to_markdown(result: &OcrPageResult) -> String {
23 let mut out = String::new();
24
25 if result.layout_boxes.is_empty() {
26 for blk in &result.blocks {
28 push_text(&mut out, blk.text.trim());
29 }
30 return out;
31 }
32
33 let mut by_layout: Vec<Vec<&OcrBlock>> = vec![Vec::new(); result.layout_boxes.len()];
36 let mut orphans: Vec<&OcrBlock> = Vec::new();
37
38 for blk in &result.blocks {
39 if blk.layout_idx >= 0 && (blk.layout_idx as usize) < by_layout.len() {
40 by_layout[blk.layout_idx as usize].push(blk);
41 } else {
42 orphans.push(blk);
43 }
44 }
45
46 for group in &mut by_layout {
48 group.sort_by_key(|b| (b.y1, b.x1));
49 }
50
51 let mut order: Vec<(i32, usize)> = result.layout_boxes.iter()
54 .enumerate()
55 .map(|(i, lb)| (lb.reading_order, i))
56 .collect();
57 order.sort_by_key(|&(ro, _)| if ro < 0 { i32::MAX } else { ro });
59
60 for (_, idx) in &order {
61 let lb = &result.layout_boxes[*idx];
62 let group = &by_layout[*idx];
63 if group.is_empty() { continue; }
64
65 let text = concat_blocks(group);
66 if text.trim().is_empty() { continue; }
67
68 match lb.semantic {
69 SemanticClass::Title => {
70 out.push_str("# ");
71 out.push_str(text.trim());
72 out.push_str("\n\n");
73 }
74 SemanticClass::Text | SemanticClass::List => {
75 push_text(&mut out, text.trim());
76 }
77 SemanticClass::Table => {
78 push_table(&mut out, *idx, &result.tables, text.trim());
79 }
80 SemanticClass::Equation => {
81 push_formula(&mut out, *idx, &result.formulas, text.trim());
82 }
83 SemanticClass::Figure => {
84 out.push_str("![figure]()\n\n");
85 }
86 SemanticClass::Header | SemanticClass::Footer => {
87 }
89 }
90 }
91
92 orphans.sort_by_key(|b| (b.y1, b.x1));
94 for blk in orphans {
95 push_text(&mut out, blk.text.trim());
96 }
97
98 out
99}
100
101pub fn to_markdown(pages: &[OcrPageResult]) -> String {
103 pages.iter()
104 .enumerate()
105 .map(|(i, p)| {
106 let header = if pages.len() > 1 {
107 format!("## Page {}\n\n", i + 1)
108 } else {
109 String::new()
110 };
111 header + &page_to_markdown(p)
112 })
113 .collect::<Vec<_>>()
114 .join("\n---\n\n")
115}
116
117fn concat_blocks(blocks: &[&OcrBlock]) -> String {
120 blocks.iter().map(|b| b.text.as_str()).collect::<Vec<_>>().join(" ")
121}
122
123fn push_text(out: &mut String, text: &str) {
124 if !text.is_empty() {
125 out.push_str(text);
126 out.push_str("\n\n");
127 }
128}
129
130fn push_table(out: &mut String, layout_idx: usize, tables: &[OcrTable], fallback: &str) {
132 if let Some(t) = tables.iter().find(|t| t.layout_idx == layout_idx as i32) {
133 if !t.gfm.is_empty() {
134 out.push_str(&t.gfm);
135 out.push('\n');
136 return;
137 }
138 }
139 push_text(out, fallback);
141}
142
143fn push_formula(out: &mut String, layout_idx: usize, formulas: &[OcrFormula], fallback: &str) {
145 if let Some(f) = formulas.iter().find(|f| f.layout_idx == layout_idx as i32) {
146 if !f.latex.is_empty() {
147 out.push_str("$$\n");
148 out.push_str(f.latex.trim());
149 out.push_str("\n$$\n\n");
150 return;
151 }
152 }
153 push_text(out, fallback);
155}