use ppocr_rs::SemanticClass;
use crate::engine::{OcrBlock, OcrFormula, OcrPageResult, OcrTable};
pub fn page_to_markdown(result: &OcrPageResult) -> String {
let mut out = String::new();
if result.layout_boxes.is_empty() {
for blk in &result.blocks {
push_text(&mut out, blk.text.trim());
}
return out;
}
let mut by_layout: Vec<Vec<&OcrBlock>> = vec![Vec::new(); result.layout_boxes.len()];
let mut orphans: Vec<&OcrBlock> = Vec::new();
for blk in &result.blocks {
if blk.layout_idx >= 0 && (blk.layout_idx as usize) < by_layout.len() {
by_layout[blk.layout_idx as usize].push(blk);
} else {
orphans.push(blk);
}
}
for group in &mut by_layout {
group.sort_by_key(|b| (b.y1, b.x1));
}
let mut order: Vec<(i32, usize)> = result.layout_boxes.iter()
.enumerate()
.map(|(i, lb)| (lb.reading_order, i))
.collect();
order.sort_by_key(|&(ro, _)| if ro < 0 { i32::MAX } else { ro });
for (_, idx) in &order {
let lb = &result.layout_boxes[*idx];
let group = &by_layout[*idx];
if group.is_empty() { continue; }
let text = concat_blocks(group);
if text.trim().is_empty() { continue; }
match lb.semantic {
SemanticClass::Title => {
out.push_str("# ");
out.push_str(text.trim());
out.push_str("\n\n");
}
SemanticClass::Text | SemanticClass::List => {
push_text(&mut out, text.trim());
}
SemanticClass::Table => {
push_table(&mut out, *idx, &result.tables, text.trim());
}
SemanticClass::Equation => {
push_formula(&mut out, *idx, &result.formulas, text.trim());
}
SemanticClass::Figure => {
out.push_str("![figure]()\n\n");
}
SemanticClass::Header | SemanticClass::Footer => {
}
}
}
orphans.sort_by_key(|b| (b.y1, b.x1));
for blk in orphans {
push_text(&mut out, blk.text.trim());
}
out
}
pub fn to_markdown(pages: &[OcrPageResult]) -> String {
pages.iter()
.enumerate()
.map(|(i, p)| {
let header = if pages.len() > 1 {
format!("## Page {}\n\n", i + 1)
} else {
String::new()
};
header + &page_to_markdown(p)
})
.collect::<Vec<_>>()
.join("\n---\n\n")
}
fn concat_blocks(blocks: &[&OcrBlock]) -> String {
blocks.iter().map(|b| b.text.as_str()).collect::<Vec<_>>().join(" ")
}
fn push_text(out: &mut String, text: &str) {
if !text.is_empty() {
out.push_str(text);
out.push_str("\n\n");
}
}
fn push_table(out: &mut String, layout_idx: usize, tables: &[OcrTable], fallback: &str) {
if let Some(t) = tables.iter().find(|t| t.layout_idx == layout_idx as i32) {
if !t.gfm.is_empty() {
out.push_str(&t.gfm);
out.push('\n');
return;
}
}
push_text(out, fallback);
}
fn push_formula(out: &mut String, layout_idx: usize, formulas: &[OcrFormula], fallback: &str) {
if let Some(f) = formulas.iter().find(|f| f.layout_idx == layout_idx as i32) {
if !f.latex.is_empty() {
out.push_str("$$\n");
out.push_str(f.latex.trim());
out.push_str("\n$$\n\n");
return;
}
}
push_text(out, fallback);
}