use std::rc::Rc;
use crate::model::RunProperties;
use crate::render::dimension::Pt;
use crate::render::geometry::PtSize;
use crate::render::resolve::color::RgbColor;
use crate::render::resolve::fonts::effective_font;
use crate::render::resolve::images::MediaEntry;
mod collect;
mod text;
pub use collect::{collect_fragments, FieldContext, FragmentCtx};
pub(super) const SUPERSCRIPT_FONT_SIZE_RATIO: f32 = 0.58;
pub(super) const SUPERSCRIPT_ASCENT_OFFSET_RATIO: f32 = 0.33;
pub(super) const SUBSCRIPT_HEIGHT_OFFSET_RATIO: f32 = 0.08;
#[derive(Clone, Debug)]
pub struct FontProps {
pub family: Rc<str>,
pub size: Pt,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub char_spacing: Pt,
pub underline_position: Pt,
pub underline_thickness: Pt,
}
#[derive(Clone, Copy, Debug)]
pub struct TextMetrics {
pub ascent: Pt,
pub descent: Pt,
pub leading: Pt,
}
impl TextMetrics {
pub fn height(&self) -> Pt {
self.ascent + self.descent
}
pub fn line_height(&self) -> Pt {
self.ascent + self.descent + self.leading
}
}
#[derive(Clone, Copy, Debug)]
pub struct FragmentBorder {
pub width: Pt,
pub color: RgbColor,
pub space: Pt,
}
#[derive(Clone, Debug)]
pub enum Fragment {
Text {
text: Rc<str>,
font: FontProps,
color: RgbColor,
shading: Option<RgbColor>,
border: Option<FragmentBorder>,
width: Pt,
trimmed_width: Pt,
metrics: TextMetrics,
hyperlink_url: Option<String>,
baseline_offset: Pt,
text_offset: Pt,
},
Image {
size: PtSize,
rel_id: String,
image_data: Option<MediaEntry>,
},
Tab {
line_height: Pt,
fitting_width: Option<Pt>,
},
LineBreak {
line_height: Pt,
},
ColumnBreak,
Bookmark {
name: String,
},
}
impl Fragment {
pub fn width(&self) -> Pt {
match self {
Fragment::Text { width, .. } => *width,
Fragment::Image { size, .. } => size.width,
Fragment::Tab { fitting_width, .. } => fitting_width.unwrap_or(MIN_TAB_WIDTH),
Fragment::LineBreak { .. } | Fragment::ColumnBreak | Fragment::Bookmark { .. } => {
Pt::ZERO
}
}
}
pub fn trimmed_width(&self) -> Pt {
match self {
Fragment::Text { trimmed_width, .. } => *trimmed_width,
other => other.width(),
}
}
pub fn height(&self) -> Pt {
match self {
Fragment::Text { metrics, .. } => metrics.height(),
Fragment::Image { size, .. } => size.height,
Fragment::Tab { line_height, .. } | Fragment::LineBreak { line_height } => *line_height,
Fragment::ColumnBreak | Fragment::Bookmark { .. } => Pt::ZERO,
}
}
pub fn is_line_break(&self) -> bool {
matches!(self, Fragment::LineBreak { .. } | Fragment::ColumnBreak)
}
pub fn font_props(&self) -> Option<&FontProps> {
match self {
Fragment::Text { font, .. } => Some(font),
_ => None,
}
}
}
pub const MIN_TAB_WIDTH: Pt = Pt::new(1.0);
pub fn font_props_from_run(
rp: &RunProperties,
default_family: &str,
default_size: Pt,
) -> FontProps {
let family = effective_font(&rp.fonts).unwrap_or(default_family);
let size = rp.font_size.map(Pt::from).unwrap_or(default_size);
let char_spacing = rp.spacing.map(Pt::from).unwrap_or(Pt::ZERO);
FontProps {
family: Rc::from(family),
size,
bold: rp.bold.unwrap_or(false),
italic: rp.italic.unwrap_or(false),
underline: rp.underline.is_some(),
char_spacing,
underline_position: Pt::ZERO,
underline_thickness: Pt::ZERO,
}
}
pub fn to_roman_lower(mut n: u32) -> String {
const VALS: [(u32, &str); 13] = [
(1000, "m"),
(900, "cm"),
(500, "d"),
(400, "cd"),
(100, "c"),
(90, "xc"),
(50, "l"),
(40, "xl"),
(10, "x"),
(9, "ix"),
(5, "v"),
(4, "iv"),
(1, "i"),
];
let mut s = String::new();
for &(val, sym) in &VALS {
while n >= val {
s.push_str(sym);
n -= val;
}
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn font_props_default_fallback() {
let rp = RunProperties::default();
let fp = font_props_from_run(&rp, "Helvetica", Pt::new(12.0));
assert_eq!(&*fp.family, "Helvetica");
assert_eq!(fp.size.raw(), 12.0);
assert!(!fp.bold);
assert!(!fp.italic);
}
}