use std::sync::Arc;
use crate::types::internal::{ElementKind, InternalDocument, InternalElement};
use crate::types::ocr_elements::{OcrConfidence, OcrElement};
use crate::types::page::PageContent;
use crate::types::tables::Table;
pub(super) fn build_pages(doc: &InternalDocument) -> Option<Vec<PageContent>> {
let mut page_map: std::collections::BTreeMap<u32, Vec<&InternalElement>> = std::collections::BTreeMap::new();
for elem in &doc.elements {
if let Some(page) = elem.page {
page_map.entry(page).or_default().push(elem);
}
}
if page_map.is_empty() {
return None;
}
let arc_tables: Vec<Arc<Table>> = doc.tables.iter().map(|t| Arc::new(t.clone())).collect();
let pages: Vec<PageContent> = page_map
.into_iter()
.map(|(page_num, elems)| {
let mut content = String::new();
let mut tables = Vec::new();
let mut image_indices = Vec::new();
for elem in &elems {
if !crate::rendering::common::is_body_element(elem) {
continue;
}
if elem.kind.is_container_start() || elem.kind.is_container_end() {
continue;
}
match elem.kind {
ElementKind::Table { table_index } => {
if let Some(arc_table) = arc_tables.get(table_index as usize) {
tables.push(Arc::clone(arc_table));
}
}
ElementKind::Image { image_index } if (image_index as usize) < doc.images.len() => {
image_indices.push(image_index);
}
_ => {}
}
if !elem.text.is_empty() {
if !content.is_empty() {
content.push_str("\n\n");
}
content.push_str(&elem.text);
}
}
PageContent {
page_number: page_num,
content,
tables,
image_indices,
image_preprocessing: None,
hierarchy: None,
is_blank: None,
layout_regions: None,
speaker_notes: None,
section_name: None,
sheet_name: None,
ocr_confidence: None,
native_content: None,
}
})
.collect();
Some(pages)
}
pub(super) fn apply_page_content_format(
pages: Option<Vec<PageContent>>,
doc: &InternalDocument,
output_format: &crate::core::config::OutputFormat,
) -> Option<Vec<PageContent>> {
use crate::core::config::OutputFormat;
let renderer: fn(&InternalDocument) -> String = match output_format {
OutputFormat::Markdown => crate::rendering::render_markdown,
OutputFormat::Djot => crate::rendering::render_djot,
OutputFormat::Html => crate::rendering::render_html,
OutputFormat::DocTags => crate::rendering::render_doctags,
OutputFormat::Plain | OutputFormat::Json | OutputFormat::Custom(_) => {
return pages;
}
};
let pages = pages?;
let elements_by_page = compute_page_membership(doc);
if elements_by_page.is_empty() {
return Some(pages);
}
let pages = pages
.into_iter()
.map(|page| match elements_by_page.get(&page.page_number) {
Some(elem_indices) => render_sub_page(page, elem_indices, doc, renderer),
None => page,
})
.collect();
Some(pages)
}
fn compute_page_membership(doc: &InternalDocument) -> std::collections::BTreeMap<u32, Vec<usize>> {
let mut next_tagged_page: Vec<Option<u32>> = vec![None; doc.elements.len()];
let mut seen: Option<u32> = None;
for (idx, elem) in doc.elements.iter().enumerate().rev() {
if elem.page.is_some() {
seen = elem.page;
}
next_tagged_page[idx] = seen;
}
let mut prev_tagged_page: Vec<Option<u32>> = vec![None; doc.elements.len()];
seen = None;
for (idx, elem) in doc.elements.iter().enumerate() {
prev_tagged_page[idx] = seen;
if elem.page.is_some() {
seen = elem.page;
}
}
let mut elements_by_page: std::collections::BTreeMap<u32, Vec<usize>> = std::collections::BTreeMap::new();
for (idx, elem) in doc.elements.iter().enumerate() {
let page_num = elem.page.or_else(|| {
if elem.kind.is_container_start() {
next_tagged_page[idx]
} else if elem.kind.is_container_end() {
prev_tagged_page[idx]
} else {
None
}
});
if let Some(page_num) = page_num {
elements_by_page.entry(page_num).or_default().push(idx);
}
}
elements_by_page
}
fn render_sub_page(
mut page: PageContent,
elem_indices: &[usize],
doc: &InternalDocument,
renderer: fn(&InternalDocument) -> String,
) -> PageContent {
let mut table_remap: ahash::AHashMap<u32, u32> = ahash::AHashMap::new();
let mut sub_tables: Vec<Table> = Vec::new();
let mut image_remap: ahash::AHashMap<u32, u32> = ahash::AHashMap::new();
let mut sub_images: Vec<crate::types::ExtractedImage> = Vec::new();
for &i in elem_indices {
match doc.elements[i].kind {
ElementKind::Table { table_index } if !table_remap.contains_key(&table_index) => {
let new_idx = sub_tables.len() as u32;
table_remap.insert(table_index, new_idx);
if let Some(t) = doc.tables.get(table_index as usize) {
sub_tables.push(t.clone());
}
}
ElementKind::Image { image_index } if !image_remap.contains_key(&image_index) => {
let new_idx = sub_images.len() as u32;
image_remap.insert(image_index, new_idx);
if let Some(img) = doc.images.get(image_index as usize) {
sub_images.push(img.clone());
}
}
_ => {}
}
}
let elements: Vec<InternalElement> = elem_indices
.iter()
.map(|&i| {
let mut elem = doc.elements[i].clone();
match elem.kind {
ElementKind::Table { ref mut table_index } => {
if let Some(&new_idx) = table_remap.get(table_index) {
*table_index = new_idx;
}
}
ElementKind::Image { ref mut image_index } => {
if let Some(&new_idx) = image_remap.get(image_index) {
*image_index = new_idx;
}
}
_ => {}
}
elem
})
.collect();
let mut sub_doc = InternalDocument::new(&doc.source_format);
sub_doc.elements = elements;
sub_doc.tables = sub_tables;
sub_doc.images = sub_images;
sub_doc.ocr_text_only = doc.ocr_text_only;
sub_doc.append_ocr_text = doc.append_ocr_text;
let rendered = renderer(&sub_doc);
if !rendered.is_empty() {
page.content = rendered;
}
page
}
pub(super) fn build_ocr_elements(doc: &InternalDocument) -> Option<Vec<OcrElement>> {
let ocr_elems: Vec<OcrElement> = doc
.elements
.iter()
.filter_map(|elem| {
if let ElementKind::OcrText { level } = elem.kind {
let geometry = elem.ocr_geometry.clone().unwrap_or_default();
let confidence = elem.ocr_confidence.clone().unwrap_or(OcrConfidence {
detection: None,
recognition: 0.0,
});
Some(OcrElement {
text: elem.text.clone(),
geometry,
confidence,
level,
rotation: elem.ocr_rotation.clone(),
page_number: elem.page.unwrap_or(1),
parent_id: None,
backend_metadata: std::collections::HashMap::new(),
})
} else {
None
}
})
.collect();
if ocr_elems.is_empty() { None } else { Some(ocr_elems) }
}