use crate::doc::format_pdf_string;
use crate::writer::{PdfWriter, Ref};
#[derive(Clone, Debug)]
pub enum PdfStructNode {
Elem {
tag: &'static str,
alt: Option<String>,
attrs: Option<&'static str>,
children: Vec<PdfStructNode>,
},
ContentRef { page_index: usize, mcid: u32 },
}
struct StructRefTree {
r: Ref,
children: Vec<StructRefTree>,
}
fn alloc_struct_refs(w: &mut PdfWriter, node: &PdfStructNode) -> StructRefTree {
let PdfStructNode::Elem { children, .. } = node else {
unreachable!("alloc_struct_refs is only ever called on Elem nodes — see write_struct_elem's ContentRef branch, which never recurses into this")
};
StructRefTree {
r: w.alloc(),
children: children
.iter()
.filter(|c| matches!(c, PdfStructNode::Elem { .. }))
.map(|c| alloc_struct_refs(w, c))
.collect(),
}
}
type ParentTree = Vec<Vec<Option<Ref>>>;
fn record_parent(parent_tree: &mut ParentTree, page_index: usize, mcid: u32, elem_ref: Ref) {
let mcid = mcid as usize;
let row = &mut parent_tree[page_index];
if row.len() <= mcid {
row.resize(mcid + 1, None);
}
row[mcid] = Some(elem_ref);
}
fn write_struct_elem(
w: &mut PdfWriter,
node: &PdfStructNode,
ref_tree: &StructRefTree,
parent_ref: Ref,
page_refs: &[Ref],
parent_tree: &mut ParentTree,
) {
let PdfStructNode::Elem { tag, alt, attrs, children } = node else {
unreachable!("only ever called on Elem nodes")
};
let mut elem_children = ref_tree.children.iter();
let k_entries: Vec<String> = children
.iter()
.map(|child| match child {
PdfStructNode::Elem { .. } => {
let child_ref_tree = elem_children
.next()
.expect("one StructRefTree per Elem child, allocated in the same order");
write_struct_elem(w, child, child_ref_tree, ref_tree.r, page_refs, parent_tree);
child_ref_tree.r.write()
}
PdfStructNode::ContentRef { page_index, mcid } => {
let page_ref = page_refs[*page_index];
record_parent(parent_tree, *page_index, *mcid, ref_tree.r);
format!("<< /Type /MCR /Pg {} /MCID {} >>", page_ref.write(), mcid)
}
})
.collect();
let alt_entry = match alt {
Some(a) => format!(" /Alt {}", format_pdf_string(a)),
None => String::new(),
};
let attrs_entry = match attrs {
Some(a) => format!(" /A << {a} >>"),
None => String::new(),
};
w.object(
ref_tree.r,
&format!(
"<< /Type /StructElem /S /{tag} /P {} /K [{}]{alt_entry}{attrs_entry} >>",
parent_ref.write(),
k_entries.join(" ")
),
);
}
pub(crate) fn write_struct_tree(w: &mut PdfWriter, root: &PdfStructNode, page_refs: &[Ref]) -> (Ref, Vec<usize>) {
let struct_tree_root_ref = w.alloc();
let root_ref_tree = alloc_struct_refs(w, root);
let mut parent_tree: ParentTree = vec![Vec::new(); page_refs.len()];
write_struct_elem(w, root, &root_ref_tree, struct_tree_root_ref, page_refs, &mut parent_tree);
let parent_tree_ref = w.alloc();
let mut nums = Vec::with_capacity(parent_tree.len());
for (page_index, row) in parent_tree.iter().enumerate() {
let entries: Vec<String> = row
.iter()
.map(|r| r.map(|rr| rr.write()).unwrap_or_else(|| "null".to_string()))
.collect();
nums.push(format!("{page_index} [{}]", entries.join(" ")));
}
w.object(parent_tree_ref, &format!("<< /Nums [{}] >>", nums.join(" ")));
w.object(
struct_tree_root_ref,
&format!(
"<< /Type /StructTreeRoot /K [{}] /ParentTree {} /ParentTreeNextKey {} >>",
root_ref_tree.r.write(),
parent_tree_ref.write(),
page_refs.len()
),
);
(struct_tree_root_ref, (0..page_refs.len()).collect())
}