use crate::core::image::OcrImage;
use crate::core::layout::{LayoutResult, ReadingOrder, TextRegion};
use crate::layout::{ColumnDetector, ColumnPartition, LineDetector, TextLine};
use crate::utils::Result;
pub struct TextOrdering {
line_detector: LineDetector,
column_detector: ColumnDetector,
}
impl TextOrdering {
pub fn new(grid_size: u32, resolution: u32) -> Self {
Self {
line_detector: LineDetector::new(grid_size, 1.5, 10),
column_detector: ColumnDetector::new(grid_size, resolution, false, 0.5),
}
}
pub fn order_text_regions(
&self,
image: &OcrImage,
text_regions: &[TextRegion],
) -> Result<Vec<TextRegion>> {
if text_regions.is_empty() {
return Ok(Vec::new());
}
let columns = self.column_detector.find_columns(image, text_regions)?;
if columns.len() > 1 {
self.order_by_columns(columns)
} else {
let lines = self.line_detector.detect_lines(text_regions)?;
self.order_by_lines(lines)
}
}
fn order_by_columns(&self, columns: Vec<ColumnPartition>) -> Result<Vec<TextRegion>> {
let column_order = self.column_detector.determine_reading_order(&columns);
let mut ordered_regions = Vec::new();
for &col_idx in &column_order {
let column = &columns[col_idx];
let lines = self.line_detector.detect_lines(&column.text_regions)?;
let line_ordered = self.order_by_lines(lines)?;
ordered_regions.extend(line_ordered);
}
Ok(ordered_regions)
}
fn order_by_lines(&self, lines: Vec<TextLine>) -> Result<Vec<TextRegion>> {
let mut ordered_regions = Vec::new();
for line in lines {
ordered_regions.extend(line.text_regions);
}
Ok(ordered_regions)
}
pub fn determine_reading_order(
&self,
image: &OcrImage,
layout: &mut LayoutResult,
) -> Result<()> {
let ordered_regions = self.order_text_regions(image, &layout.text_regions)?;
layout.text_regions = ordered_regions;
let columns = self
.column_detector
.find_columns(image, &layout.text_regions)?;
if columns.len() > 1 {
layout.reading_order = ReadingOrder::MultiColumn;
} else {
layout.reading_order = ReadingOrder::TopToBottom;
}
Ok(())
}
pub fn set_cjk_script(&mut self, cjk_script: bool) {
self.column_detector.set_cjk_script(cjk_script);
}
}
impl Default for TextOrdering {
fn default() -> Self {
Self::new(20, 300)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::text::BoundingBox;
#[test]
fn test_text_ordering_creation() {
let _ordering = TextOrdering::new(20, 300);
}
#[test]
fn test_single_column_ordering() {
let ordering = TextOrdering::default();
let regions = vec![
TextRegion::new(
"1".to_string(),
BoundingBox::new(0, 0, 50, 20),
"First".to_string(),
),
TextRegion::new(
"2".to_string(),
BoundingBox::new(0, 30, 50, 50),
"Second".to_string(),
),
];
let image = OcrImage::new(image::DynamicImage::new_rgb8(100, 100), 300);
let ordered = ordering.order_text_regions(&image, ®ions).unwrap();
assert_eq!(ordered.len(), 2);
assert_eq!(ordered[0].id, "1");
assert_eq!(ordered[1].id, "2");
}
}