use crate::ParseError;
use crate::context::KatexContext;
use crate::dom_tree::{Anchor, DomSpan, HtmlDomFragment, HtmlDomNode, Span, SvgNode, SymbolNode};
use crate::font_metrics::get_character_metrics;
use crate::font_metrics_data::CharacterMetrics;
use crate::namespace::KeyMap;
use crate::options::{FontShape, FontWeight, Options};
use crate::parser::parse_node::AnyParseNode;
use crate::spacing_data::Measurement;
use crate::symbols::{Font, Mode, is_ligature};
use crate::tree::DocumentFragment;
use crate::types::ClassList;
use crate::types::{CssProperty, CssStyle, ParseErrorKind};
use crate::units::make_em;
use crate::wide_character::get_wide_character_font;
use alloc::borrow::Cow;
use bon::bon;
use phf::phf_map;
pub const FONT_MAP: phf::Map<&str, FontMapEntry> = phf_map! {
"mathbf" => FontMapEntry {
variant: "bold",
font_name: "Main-Bold",
},
"mathrm" => FontMapEntry {
variant: "normal",
font_name: "Main-Regular",
},
"textit" => FontMapEntry {
variant: "italic",
font_name: "Main-Italic",
},
"mathit" => FontMapEntry {
variant: "italic",
font_name: "Main-Italic",
},
"mathnormal" => FontMapEntry {
variant: "italic",
font_name: "Math-Italic",
},
"mathbb" => FontMapEntry {
variant: "double-struck",
font_name: "AMS-Regular",
},
"mathcal" => FontMapEntry {
variant: "script",
font_name: "Caligraphic-Regular",
},
"mathscr" => FontMapEntry {
variant: "script",
font_name: "Script-Regular",
},
"mathfrak" => FontMapEntry {
variant: "fraktur",
font_name: "Fraktur-Regular",
},
"mathsf" => FontMapEntry {
variant: "sans-serif",
font_name: "SansSerif-Regular",
},
"mathsfit" => FontMapEntry {
variant: "sans-serif-italic",
font_name: "SansSerif-Italic",
},
"mathtt" => FontMapEntry {
variant: "monospace",
font_name: "Typewriter-Regular",
},
"boldsymbol" => FontMapEntry {
variant: "bold-italic",
font_name: "Math-BoldItalic",
},
};
#[derive(Debug, Clone)]
pub struct FontMapEntry {
pub variant: &'static str,
pub font_name: &'static str,
}
#[derive(Debug, Clone)]
pub struct SymbolLookup {
pub value: char,
pub metrics: Option<CharacterMetrics>,
}
#[derive(Debug, bon::Builder)]
pub struct VListElem {
pub elem: HtmlDomNode,
pub shift: Option<f64>,
pub margin_left: Option<String>,
pub margin_right: Option<String>,
pub wrapper_classes: Option<ClassList>,
pub wrapper_style: Option<CssStyle>,
}
#[derive(Debug, Clone)]
pub struct VListKern {
pub size: f64,
}
impl From<f64> for VListKern {
fn from(size: f64) -> Self {
Self { size }
}
}
#[derive(Debug)]
pub enum VListChild {
Elem(Box<VListElem>),
Kern(VListKern),
}
impl From<VListElem> for VListChild {
fn from(elem: VListElem) -> Self {
Self::Elem(Box::new(elem))
}
}
#[derive(Debug)]
pub struct VListElemAndShift {
pub elem: HtmlDomNode,
pub shift: f64,
pub margin_left: Option<String>,
pub margin_right: Option<String>,
pub wrapper_classes: Option<ClassList>,
pub wrapper_style: Option<CssStyle>,
}
#[bon]
impl VListElemAndShift {
#[builder]
pub const fn new(
elem: HtmlDomNode,
shift: f64,
margin_left: Option<String>,
margin_right: Option<String>,
wrapper_classes: Option<ClassList>,
wrapper_style: Option<CssStyle>,
) -> Self {
Self {
elem,
shift,
margin_left,
margin_right,
wrapper_classes,
wrapper_style,
}
}
}
#[derive(Debug)]
pub enum VListParam {
IndividualShift {
children: Vec<VListElemAndShift>,
},
Top {
position_data: f64,
children: Vec<VListChild>,
},
Bottom {
position_data: f64,
children: Vec<VListChild>,
},
Shift {
position_data: f64,
children: Vec<VListChild>,
},
FirstBaseline {
children: Vec<VListChild>,
},
}
#[derive(Debug)]
pub struct VListChildrenAndDepth {
pub children: Vec<VListChild>,
pub depth: f64,
}
fn size_element_from_children_dom(node: &mut DomSpan) {
let (height, depth, max_font_size) = size_properties(&node.children);
node.height = height;
node.depth = depth;
node.max_font_size = max_font_size;
}
#[must_use]
pub fn make_span<T: Into<ClassList>>(
classes: T,
children: Vec<HtmlDomNode>,
options: Option<&Options>,
style: Option<CssStyle>,
) -> DomSpan {
let classes = classes.into();
let mut node = Span::from_parts(children, classes, style, options);
size_element_from_children_dom(&mut node);
node
}
pub fn make_v_list(params: VListParam, _options: &Options) -> Result<DomSpan, ParseError> {
let VListChildrenAndDepth { children, depth } = get_v_list_children_and_depth(params)?;
let mut pstrut_size = 0.0f64;
for child in &children {
if let VListChild::Elem(elem) = child {
pstrut_size = pstrut_size
.max(elem.elem.max_font_size())
.max(elem.elem.height());
}
}
pstrut_size += 2.0; let mut pstrut = make_span("pstrut", vec![], None, None);
pstrut
.style
.insert(CssProperty::Height, make_em(pstrut_size));
let mut real_children: Vec<HtmlDomNode> = Vec::new();
let mut min_pos = depth;
let mut max_pos = depth;
let mut curr_pos = depth;
for child in children {
match child {
VListChild::Kern(kern) => {
curr_pos += kern.size;
}
VListChild::Elem(child) => {
let elem = child.elem;
let classes = child.wrapper_classes.unwrap_or_default();
let style = child.wrapper_style.unwrap_or_default();
let elem_height = elem.height();
let elem_depth = elem.depth();
let mut child_wrap = make_span(
classes,
vec![pstrut.clone().into(), elem],
None,
Some(style),
);
child_wrap.style.insert(
CssProperty::Top,
make_em(-pstrut_size - curr_pos - elem_depth),
);
if let Some(margin_left) = &child.margin_left {
child_wrap
.style
.insert(CssProperty::MarginLeft, margin_left.clone());
}
if let Some(margin_right) = &child.margin_right {
child_wrap
.style
.insert(CssProperty::MarginRight, margin_right.clone());
}
real_children.push(child_wrap.into());
curr_pos += elem_height + elem_depth;
}
}
min_pos = min_pos.min(curr_pos);
max_pos = max_pos.max(curr_pos);
}
let mut vlist = make_span("vlist", real_children, None, None);
vlist.style.insert(CssProperty::Height, make_em(max_pos));
let rows: Vec<HtmlDomNode> = if min_pos < 0.0 {
let empty_span = make_span(ClassList::Empty, vec![], None, None);
let mut depth_strut = make_span("vlist", vec![empty_span.into()], None, None);
depth_strut
.style
.insert(CssProperty::Height, make_em(-min_pos));
let top_strut = make_span(
"vlist-s",
vec![HtmlDomNode::Symbol(
SymbolNode::builder().text("\u{200b}").build(),
)],
None,
None,
);
vec![
make_span("vlist-r", vec![vlist.into(), top_strut.into()], None, None).into(),
make_span("vlist-r", vec![depth_strut.into()], None, None).into(),
]
} else {
vec![make_span("vlist-r", vec![vlist.into()], None, None).into()]
};
let vtable_classes = if rows.len() == 2 {
ClassList::Const(&["vlist-t", "vlist-t2"])
} else {
ClassList::Const(&["vlist-t"])
};
let mut vtable = make_span(vtable_classes, rows, None, None);
vtable.height = max_pos;
vtable.depth = -min_pos;
Ok(vtable)
}
pub fn get_v_list_children_and_depth(
params: VListParam,
) -> Result<VListChildrenAndDepth, ParseError> {
match params {
VListParam::IndividualShift {
children: old_children,
} => {
let mut iter = old_children.into_iter();
let Some(first_child) = iter.next() else {
return Ok(VListChildrenAndDepth {
children: Vec::new(),
depth: 0.0,
});
};
let VListElemAndShift {
elem,
shift,
margin_left,
margin_right,
wrapper_classes,
wrapper_style,
} = first_child;
let elem_height = elem.height();
let elem_depth = elem.depth();
let depth = -shift - elem_depth;
let mut curr_pos = depth;
let mut prev_height = elem_height;
let mut prev_depth = elem_depth;
let mut children: Vec<VListChild> = Vec::new();
children.push(
VListElem {
elem,
shift: Some(shift),
margin_left,
margin_right,
wrapper_classes,
wrapper_style,
}
.into(),
);
for child in iter {
let VListElemAndShift {
elem,
shift,
margin_left,
margin_right,
wrapper_classes,
wrapper_style,
} = child;
let elem_height = elem.height();
let elem_depth = elem.depth();
let diff = -shift - curr_pos - elem_depth;
let size = diff - (prev_height + prev_depth);
curr_pos += diff;
children.push(VListChild::Kern(VListKern { size }));
children.push(
VListElem {
elem,
shift: Some(shift),
margin_left,
margin_right,
wrapper_classes,
wrapper_style,
}
.into(),
);
prev_height = elem_height;
prev_depth = elem_depth;
}
Ok(VListChildrenAndDepth { children, depth })
}
VListParam::Top {
position_data,
children: vlist_children,
} => {
let mut bottom = position_data;
for child in &vlist_children {
bottom -= match child {
VListChild::Kern(kern) => kern.size,
VListChild::Elem(elem) => elem.elem.height() + elem.elem.depth(),
};
}
let depth = bottom;
Ok(VListChildrenAndDepth {
children: vlist_children,
depth,
})
}
VListParam::Bottom {
position_data,
children: vlist_children,
} => {
let depth = -position_data;
Ok(VListChildrenAndDepth {
children: vlist_children,
depth,
})
}
VListParam::Shift {
position_data,
children: vlist_children,
} => {
let first_elem = vlist_children.iter().find_map(|child| {
if let VListChild::Elem(elem) = child {
Some(elem)
} else {
None
}
});
let depth = first_elem
.map_or_else(|| -position_data, |elem| -elem.elem.depth() - position_data);
Ok(VListChildrenAndDepth {
children: vlist_children,
depth,
})
}
VListParam::FirstBaseline {
children: vlist_children,
} => {
let first_elem = vlist_children.iter().find_map(|child| {
if let VListChild::Elem(elem) = child {
Some(elem)
} else {
None
}
});
let depth = first_elem.map_or(0.0, |elem| -elem.elem.depth());
Ok(VListChildrenAndDepth {
children: vlist_children,
depth,
})
}
}
}
pub fn lookup_symbol(
ctx: &KatexContext,
value: &str,
font_name: &str,
mode: Mode,
) -> Result<Option<SymbolLookup>, ParseError> {
let query = if let Some(char_info) = ctx.symbols.get(mode, value)
&& let Some(replaced) = char_info.replace
{
replaced
} else {
value
.chars()
.next()
.ok_or_else(|| ParseError::new(ParseErrorKind::EmptyLookupSymbolInput))?
};
let metrics = get_character_metrics(ctx, query, font_name, mode)?;
Ok(Some(SymbolLookup {
value: query,
metrics: metrics.copied(),
}))
}
pub fn make_symbol(
ctx: &KatexContext,
value: &str,
font_name: &str,
mode: Mode,
options: Option<&Options>,
classes: ClassList,
) -> Result<SymbolNode, ParseError> {
let (metrics, value) = lookup_symbol(ctx, value, font_name, mode)?.map_or_else(
|| (None, value.to_owned()),
|lookup| (lookup.metrics, lookup.value.to_string()),
);
let (height, depth, italic, skew, width) = metrics.map_or((0.0, 0.0, 0.0, 0.0, 0.0), |m| {
let italic = if mode == Mode::Text || options.as_ref().is_some_and(|o| o.font == "mathit") {
0.0
} else {
m.italic
};
(m.height, m.depth, italic, m.skew, m.width)
});
let mut classes_vec = classes;
let mut style = CssStyle::with_capacity(2);
if let Some(options) = options {
if options.style.is_tight() {
classes_vec.push("mtight");
}
if let Some(color) = options.get_color() {
style.insert(CssProperty::Color, color);
}
}
let mut symbol = SymbolNode::builder()
.text(&value)
.height(height)
.depth(depth)
.italic(italic)
.skew(skew)
.width(width)
.classes(classes_vec)
.style(style)
.build();
if let Some(options) = options {
symbol.max_font_size = options.size_multiplier;
}
Ok(symbol)
}
pub fn mathsym(
ctx: &KatexContext,
value: &str,
mode: Mode,
options: &Options,
classes: ClassList,
) -> Result<SymbolNode, ParseError> {
if options.font == "boldsymbol"
&& lookup_symbol(ctx, value, "Main-Bold", mode)?
.is_some_and(|lookup| lookup.metrics.is_some())
{
let mut combined_classes = classes;
combined_classes.push("mathbf");
make_symbol(
ctx,
value,
"Main-Bold",
mode,
Some(options),
combined_classes,
)
} else if value == "\\"
|| ctx
.symbols
.get(mode, value)
.is_some_and(|info| matches!(info.font, Font::Main))
{
make_symbol(ctx, value, "Main-Regular", mode, Some(options), classes)
} else {
let mut combined_classes = classes;
combined_classes.push("amsrm");
make_symbol(
ctx,
value,
"AMS-Regular",
mode,
Some(options),
combined_classes,
)
}
}
#[must_use]
pub fn retrieve_text_font_name(
font_family: &str,
font_weight: &FontWeight,
font_shape: &FontShape,
) -> String {
let base_font_name = match font_family {
"amsrm" => "AMS",
"textrm" => "Main",
"textsf" => "SansSerif",
"texttt" => "Typewriter",
_ => font_family,
};
let font_styles_name = match (font_weight, font_shape) {
(FontWeight::TextBf, FontShape::TextIt) => "BoldItalic",
(FontWeight::TextBf, _) => "Bold",
(_, FontShape::TextIt) => "Italic",
_ => "Regular",
};
format!("{base_font_name}-{font_styles_name}")
}
#[inline]
fn both_single_same_mbin_or_mord(a: &ClassList, b: &ClassList) -> bool {
let mut a_iter = a.into_iter();
let mut b_iter = b.into_iter();
matches!((a_iter.next(), b_iter.next()), (Some("mbin"), Some("mbin")) | (Some("mord"), Some("mord")) if a_iter.next().is_none() && b_iter.next().is_none())
}
#[inline]
fn can_combine_symbols(prev: &SymbolNode, next: &SymbolNode) -> bool {
if both_single_same_mbin_or_mord(&prev.classes, &next.classes) {
return false;
}
if prev.skew != next.skew || prev.max_font_size != next.max_font_size {
return false;
}
if prev.classes != next.classes {
return false;
}
prev.style == next.style
}
#[inline]
pub fn push_combine_chars(chars: &mut Vec<HtmlDomNode>, node: HtmlDomNode) {
match node {
HtmlDomNode::Symbol(next_sym) => {
if let Some(HtmlDomNode::Symbol(prev_sym)) = chars.last_mut()
&& can_combine_symbols(prev_sym, &next_sym)
{
prev_sym.height = prev_sym.height.max(next_sym.height);
prev_sym.depth = prev_sym.depth.max(next_sym.depth);
prev_sym.italic = next_sym.italic;
prev_sym.text.push_str(&next_sym.text);
} else {
chars.push(HtmlDomNode::Symbol(next_sym));
}
}
other => chars.push(other),
}
}
impl KatexContext {
pub fn make_glue<T>(
&self,
measurement: &Measurement<T>,
options: &Options,
) -> Result<DomSpan, ParseError>
where
T: AsRef<str>,
{
let mut rule = make_span("mspace", vec![], Some(options), None);
let size = self.calculate_size(measurement, options)?;
rule.style.insert(CssProperty::MarginRight, make_em(size));
Ok(rule)
}
}
pub fn make_ord(
ctx: &KatexContext,
node: &AnyParseNode,
options: &Options,
) -> Result<HtmlDomNode, ParseError> {
let (mode, text, ord_type) = match node {
AnyParseNode::MathOrd(math_ord) => (math_ord.mode, &math_ord.text, Mode::Math),
AnyParseNode::TextOrd(text_ord) => (text_ord.mode, &text_ord.text, Mode::Text),
AnyParseNode::Spacing(spacing) => (spacing.mode, &spacing.text, Mode::Text),
_ => {
return Err(ParseError::new(ParseErrorKind::MakeOrdExpectedNode));
}
};
let classes = ClassList::Static("mord");
let is_font = mode == Mode::Math || (mode == Mode::Text && !options.font.is_empty());
let font_or_family = if is_font {
if options.font.is_empty() {
None
} else {
Some(&options.font)
}
} else if options.font_family.is_empty() {
None
} else {
Some(&options.font_family)
};
let mut utf16_iter = text.encode_utf16();
if let Some(code_unit) = utf16_iter.next()
&& code_unit == 0xD835
&& let Ok((font_name, font_class)) = get_wide_character_font(text, mode)
&& !font_name.is_empty()
{
let mut combined_classes = classes;
if !font_class.is_empty() {
combined_classes.push(font_class);
}
return Ok(
make_symbol(ctx, text, font_name, mode, Some(options), combined_classes)?.into(),
);
}
if let Some(font_or_family) = font_or_family {
let (font_name, font_classes) = if font_or_family == "boldsymbol" {
let font_data = bold_symbol(ctx, text, mode, ord_type)?;
(font_data.font_name, vec![Cow::Owned(font_data.font_class)])
} else if is_font {
let font_name: &str = FONT_MAP
.get(font_or_family)
.map_or(font_or_family, |entry| entry.font_name);
(
font_name.to_owned(),
vec![Cow::Owned(font_or_family.clone())],
)
} else {
let font_name =
retrieve_text_font_name(font_or_family, &options.font_weight, &options.font_shape);
(
font_name,
vec![
Cow::Owned(font_or_family.clone()),
Cow::Borrowed(options.font_weight.as_str()),
Cow::Borrowed(options.font_shape.as_str()),
],
)
};
if lookup_symbol(ctx, text, &font_name, mode)?
.is_some_and(|lookup| lookup.metrics.is_some())
{
let mut combined_classes = classes;
combined_classes.extend(font_classes);
return Ok(
make_symbol(ctx, text, &font_name, mode, Some(options), combined_classes)?.into(),
);
}
if font_name.starts_with("Typewriter") && is_ligature(text) {
let mut base_classes = classes;
base_classes.extend(font_classes);
let mut parts = Vec::new();
for ch in text.chars() {
let char_str = ch.to_string();
let symbol = make_symbol(
ctx,
&char_str,
&font_name,
mode,
Some(options),
base_classes.clone(),
)?;
parts.push(symbol.into());
}
return Ok(make_fragment(parts).into());
}
}
match ord_type {
Mode::Math => {
let mut combined_classes = classes;
combined_classes.push("mathnormal");
Ok(make_symbol(
ctx,
text,
"Math-Italic",
mode,
Some(options),
combined_classes,
)?
.into())
}
Mode::Text => {
let symbol_info = ctx.symbols.get(mode, text);
if let Some(info) = symbol_info {
match &info.font {
Font::Ams => {
let font_name = retrieve_text_font_name(
"amsrm",
&options.font_weight,
&options.font_shape,
);
let mut combined_classes = classes;
combined_classes.push("amsrm");
combined_classes.push(options.font_weight.as_str());
combined_classes.push(options.font_shape.as_str());
Ok(make_symbol(
ctx,
text,
&font_name,
mode,
Some(options),
combined_classes,
)?
.into())
}
Font::Main => {
let font_name = retrieve_text_font_name(
"textrm",
&options.font_weight,
&options.font_shape,
);
let mut combined_classes = classes;
combined_classes.push(options.font_weight.as_str());
combined_classes.push(options.font_shape.as_str());
Ok(make_symbol(
ctx,
text,
&font_name,
mode,
Some(options),
combined_classes,
)?
.into())
}
Font::Custom(font) => {
let font_name = retrieve_text_font_name(
font,
&options.font_weight,
&options.font_shape,
);
let mut combined_classes = classes;
combined_classes.push(options.font_weight.as_str());
combined_classes.push(options.font_shape.as_str());
Ok(make_symbol(
ctx,
text,
&font_name,
mode,
Some(options),
combined_classes,
)?
.into())
}
}
} else {
let font_name =
retrieve_text_font_name("textrm", &options.font_weight, &options.font_shape);
let mut combined_classes = classes;
combined_classes.push(options.font_weight.as_str());
combined_classes.push(options.font_shape.as_str());
Ok(
make_symbol(ctx, text, &font_name, mode, Some(options), combined_classes)?
.into(),
)
}
}
}
}
pub fn make_svg_span<T: Into<ClassList>>(
classes: T,
svg_node: Vec<SvgNode>,
options: &Options,
) -> DomSpan {
Span::builder()
.children(svg_node.into_iter().map(HtmlDomNode::SvgNode).collect())
.classes(classes.into())
.build(Some(options))
}
pub const VEC_SVG_DATA: (f64, f64) = (0.471, 0.714);
pub const OIINT_SIZE1: (f64, f64) = (0.957, 0.499);
pub const OIINT_SIZE2: (f64, f64) = (1.472, 0.659);
pub const OIIINT_SIZE1: (f64, f64) = (1.304, 0.499);
pub const OIIINT_SIZE2: (f64, f64) = (1.98, 0.659);
const SVG_DATA: phf::Map<&'static str, (f64, f64)> = phf_map! {
"vec" => VEC_SVG_DATA,
"oiintSize1" => OIINT_SIZE1,
"oiintSize2" => OIINT_SIZE2,
"oiiintSize1" => OIIINT_SIZE1,
"oiiintSize2" => OIIINT_SIZE2,
};
pub fn static_svg(path_name: &str, options: &Options) -> Result<DomSpan, ParseError> {
if let Some((width, height)) = SVG_DATA.get(path_name) {
use crate::build_common::make_svg_span;
use crate::dom_tree::{PathNode, SvgChildNode};
let path = PathNode {
path_name: path_name.to_owned(),
alternate: None,
};
let svg_attributes = [
("width".to_owned(), make_em(*width)),
("height".to_owned(), make_em(*height)),
("style".to_owned(), format!("width:{}", make_em(*width))),
(
"viewBox".to_owned(),
format!("0 0 {} {}", 1000.0 * width, 1000.0 * height),
),
("preserveAspectRatio".to_owned(), "xMinYMin".to_owned()),
]
.iter()
.cloned()
.collect();
let svg_node = SvgNode::builder()
.children(vec![SvgChildNode::Path(path)])
.attributes(svg_attributes)
.build();
let mut span = make_svg_span("overlay", vec![svg_node], options);
span.height = *height;
span.style.insert(CssProperty::Height, make_em(*height));
span.style.insert(CssProperty::Width, make_em(*width));
Ok(span)
} else {
use crate::build_common::make_span;
Ok(make_span(ClassList::Empty, vec![], Some(options), None))
}
}
#[derive(Debug)]
struct FontData {
font_name: String,
font_class: String,
}
fn bold_symbol(
ctx: &KatexContext,
text: &str,
mode: Mode,
ord_type: Mode,
) -> Result<FontData, ParseError> {
if ord_type != Mode::Text
&& lookup_symbol(ctx, text, "Math-BoldItalic", mode)?
.is_some_and(|lookup| lookup.metrics.is_some())
{
Ok(FontData {
font_name: "Math-BoldItalic".to_owned(),
font_class: "boldsymbol".to_owned(),
})
} else {
Ok(FontData {
font_name: "Main-Bold".to_owned(),
font_class: "mathbf".to_owned(),
})
}
}
#[must_use]
pub fn make_line_span(
class_name: &'static str,
options: &Options,
thickness: Option<f64>,
) -> DomSpan {
let mut line = make_span(class_name, vec![], Some(options), None);
let default_thickness = options.font_metrics().default_rule_thickness;
let line_thickness = thickness.unwrap_or(default_thickness);
line.height = line_thickness.max(options.min_rule_thickness);
line.style
.insert(CssProperty::BorderBottomWidth, make_em(line.height));
line.max_font_size = 1.0;
line
}
#[must_use]
pub fn make_anchor<T: Into<ClassList>>(
href: &str,
classes: T,
children: Vec<HtmlDomNode>,
options: &Options,
) -> Anchor {
let mut attributes = KeyMap::default();
attributes.insert("href".to_owned(), href.to_owned());
let mut anchor = Anchor::builder()
.children(children)
.attributes(attributes)
.classes(classes.into())
.height(0.0)
.depth(0.0)
.max_font_size(options.size_multiplier)
.build(Some(options));
let (height, depth, max_font_size) = size_properties(&anchor.children);
anchor.height = height;
anchor.depth = depth;
anchor.max_font_size = max_font_size;
anchor
}
#[must_use]
pub fn make_fragment(children: Vec<HtmlDomNode>) -> HtmlDomFragment {
let mut fragment = DocumentFragment::new(children);
let (height, depth, max_font_size) = size_properties(&fragment.children);
fragment.height = height;
fragment.depth = depth;
fragment.max_font_size = max_font_size;
fragment
}
#[must_use]
pub fn wrap_fragment(group: HtmlDomNode, options: &Options) -> HtmlDomNode {
match group {
HtmlDomNode::Fragment(fragment) => {
let span = make_span(
ClassList::Empty,
vec![HtmlDomNode::Fragment(fragment)],
Some(options),
None,
);
HtmlDomNode::DomSpan(span)
}
_ => group, }
}
fn size_properties(children: &[HtmlDomNode]) -> (f64, f64, f64) {
let mut height = 0.0;
let mut depth = 0.0;
let mut max_font_size = 0.0;
for child in children {
if child.height() > height {
height = child.height();
}
if child.depth() > depth {
depth = child.depth();
}
if child.max_font_size() > max_font_size {
max_font_size = child.max_font_size();
}
}
(height, depth, max_font_size)
}