use std::collections::HashMap;
use rdocx_oxml::styles::CT_Styles;
use crate::engine::layout_paragraph;
use crate::input::{LayoutInput, MediaRegistry};
use crate::style_resolver::NumberingState;
use oxml_layout::{Color, FontManager, LayoutLine, NoteRef, NoteStream, Result, TextSegment};
const NOTE_FONT_SIZE: f64 = 8.0;
pub const NOTE_INDENT: f64 = 12.0;
pub const NOTE_SEPARATOR_OFFSET: f64 = 6.0;
pub const SEPARATOR_WIDTH_FRACTION: f64 = 0.33;
#[derive(Debug, Clone)]
pub struct NoteLayout {
pub marker: TextSegment,
pub marker_rise: f64,
pub lines: Vec<LayoutLine>,
}
impl NoteLayout {
pub fn height_of(&self, first: usize, count: usize) -> f64 {
self.lines
.iter()
.skip(first)
.take(count)
.map(|line| line.height)
.sum()
}
pub fn height_from(&self, first: usize) -> f64 {
self.height_of(first, self.lines.len())
}
pub fn height(&self) -> f64 {
self.height_from(0)
}
}
#[derive(Debug, Clone, Default)]
pub struct NoteRegistry {
notes: HashMap<NoteRef, NoteLayout>,
continuation_separator: bool,
}
impl NoteRegistry {
pub fn build(
input: &LayoutInput,
styles: &CT_Styles,
media: &MediaRegistry,
fm: &mut FontManager,
num_state: &mut NumberingState,
content_width: f64,
) -> Result<Self> {
let mut notes = HashMap::new();
let mut continuation_separator = false;
let note_width = (content_width - NOTE_INDENT).max(1.0);
for (kind, stream) in [
(NoteStream::Footnote, input.footnotes.as_ref()),
(NoteStream::Endnote, input.endnotes.as_ref()),
]
.into_iter()
.filter_map(|(kind, stream)| stream.map(|stream| (kind, stream)))
{
if stream.has_continuation_separator() {
continuation_separator = true;
}
for note in &stream.footnotes {
let key = NoteRef {
stream: kind,
id: note.id,
};
if stream.get_by_id(note.id).is_none() || notes.contains_key(&key) {
continue;
}
let mut lines = Vec::new();
for paragraph in ¬e.paragraphs {
let block = layout_paragraph(
paragraph, note_width, styles, input, media, fm, num_state,
)?;
lines.extend(block.lines);
}
let Some(marker) = shape_marker(note.id, fm)? else {
continue;
};
notes.insert(
key,
NoteLayout {
marker,
marker_rise: NOTE_FONT_SIZE * 0.33,
lines,
},
);
}
}
Ok(NoteRegistry {
notes,
continuation_separator,
})
}
pub fn get(&self, note: NoteRef) -> Option<&NoteLayout> {
self.notes.get(¬e)
}
pub fn has_continuation_separator(&self) -> bool {
self.continuation_separator
}
}
fn shape_marker(id: i32, fm: &mut FontManager) -> Result<Option<TextSegment>> {
let text = id.to_string();
let size = NOTE_FONT_SIZE * 0.58;
let Ok(font_id) = fm.resolve_font(Some("serif"), false, false) else {
return Ok(None);
};
let Ok(shaped) = fm.shape_text(font_id, &text, size) else {
return Ok(None);
};
let metrics = fm.metrics(font_id, size)?;
Ok(Some(TextSegment {
text,
font_id,
font_size: size,
glyph_ids: shaped.glyph_ids,
advances: shaped.advances,
width: shaped.width,
ascent: metrics.ascent,
descent: metrics.descent,
line_gap: 0.0,
color: Color::BLACK,
bold: false,
italic: false,
underline: None,
strike: false,
dstrike: false,
highlight: None,
baseline_offset: 0.0,
hyperlink_url: None,
field_kind: None,
note: None,
}))
}