use alloc::vec::Vec;
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use core_maths::CoreFloat;
use fontique::{Query, QueryFamily};
use parley_engine::{FontInstance, FontMetrics, ShapedText};
use crate::resolve::{ResolveContext, ResolvedStyle};
use crate::style::Brush;
use crate::{AlignmentBaseline, BaselineShift, FontContext, FontData, InlineBox, VerticalAlign};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct StyleMetrics {
pub(crate) ascent: f32,
pub(crate) descent: f32,
pub(crate) x_height: f32,
pub(crate) over: f32,
pub(crate) under: f32,
pub(crate) baseline_offset: f32,
pub(crate) exact_baseline_offset: f32,
pub(crate) aligned_subtree_root: u16,
pub(crate) parent: u16,
pub(crate) font_size: f32,
}
pub(crate) fn resolve_style_metrics<B: Brush>(
rcx: &ResolveContext,
fcx: &mut FontContext,
styles: &[ResolvedStyle<B>],
shaped_text: &ShapedText,
quantize: bool,
out: &mut Vec<StyleMetrics>,
) {
out.clear();
out.reserve(styles.len());
let mut query = fcx.collection.query(&mut fcx.source_cache);
for (index, style) in styles.iter().enumerate() {
let font_metrics = first_available_font(&mut query, rcx, style)
.and_then(|font| {
let variations = rcx.variations(style.font_variations).unwrap_or(&[]);
shaped_run_metrics(shaped_text, &font, style.font_size, variations)
.or_else(|| FontMetrics::from_font_instance(&font, style.font_size, variations))
})
.unwrap_or_else(|| FontMetrics::fallback(style.font_size));
let line_height = style
.line_height
.resolve(style.font_size, &font_metrics, quantize);
let mut metrics = StyleMetrics::from_font(&font_metrics, line_height, quantize);
metrics.font_size = style.font_size;
if index == 0 {
metrics.parent = 0;
metrics.baseline_offset = 0.;
metrics.exact_baseline_offset = 0.;
metrics.aligned_subtree_root = 0;
} else {
let parent_index = usize::from(style.parent);
debug_assert!(
parent_index < index,
"style table must be ordered parent-first"
);
let parent_index = if parent_index < index {
parent_index
} else {
0
};
metrics.parent = parent_index as u16;
let parent = &out[parent_index];
let align = style.vertical_align;
if align.is_line_relative() {
metrics.baseline_offset = 0.;
metrics.exact_baseline_offset = 0.;
metrics.aligned_subtree_root = index as u16;
} else {
let shift = shift_from_parent(align, metrics.over, metrics.under, parent);
metrics.exact_baseline_offset = parent.exact_baseline_offset + shift;
metrics.baseline_offset = if quantize {
metrics.exact_baseline_offset.round()
} else {
metrics.exact_baseline_offset
};
metrics.aligned_subtree_root = parent.aligned_subtree_root;
}
}
out.push(metrics);
}
}
fn shaped_run_metrics(
shaped_text: &ShapedText,
font: &FontInstance,
font_size: f32,
variations: &[crate::FontVariation],
) -> Option<FontMetrics> {
if !variations.is_empty() || !font.synthesis.variation_settings().is_empty() {
return None;
}
let font_index = shaped_text.fonts().iter().position(|f| f == font)?;
let coords = shaped_text.normalized_coords();
shaped_text
.runs()
.iter()
.find(|run| {
run.font_index == font_index
&& run.font_size.to_bits() == font_size.to_bits()
&& coords[run.normalized_coords_range.clone()]
.iter()
.all(|c| c.to_bits() == 0)
})
.map(|run| run.font_metrics)
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct BoxMetrics {
pub(crate) ascent: f32,
pub(crate) descent: f32,
pub(crate) over: f32,
pub(crate) under: f32,
}
impl BoxMetrics {
pub(crate) fn from_font(font: &FontMetrics, line_height: f32, quantize: bool) -> Self {
let (ascent, descent) = if quantize {
(font.ascent.round(), font.descent.round())
} else {
(font.ascent, font.descent)
};
let half_leading = (line_height - (ascent + descent)) / 2.;
let over = if quantize {
ascent + half_leading.floor()
} else {
ascent + half_leading
};
Self {
ascent,
descent,
over,
under: line_height - over,
}
}
}
impl StyleMetrics {
fn from_font(font: &FontMetrics, line_height: f32, quantize: bool) -> Self {
let box_metrics = BoxMetrics::from_font(font, line_height, quantize);
let x_height = font.x_height.unwrap_or(box_metrics.ascent * 0.56);
Self {
ascent: box_metrics.ascent,
descent: box_metrics.descent,
x_height: if quantize { x_height.round() } else { x_height },
over: box_metrics.over,
under: box_metrics.under,
baseline_offset: 0.,
exact_baseline_offset: 0.,
aligned_subtree_root: 0,
parent: 0,
font_size: 0.,
}
}
}
pub(crate) fn shift_from_parent(
align: VerticalAlign,
over: f32,
under: f32,
parent: &StyleMetrics,
) -> f32 {
let alignment = match align.alignment {
AlignmentBaseline::Baseline => 0.,
AlignmentBaseline::TextTop => parent.ascent - over,
AlignmentBaseline::TextBottom => under - parent.descent,
AlignmentBaseline::Middle => parent.x_height / 2. - (over - under) / 2.,
};
let shift = match align.shift {
BaselineShift::Length(length) => length,
BaselineShift::Sub => -(parent.font_size / 5.),
BaselineShift::Super => parent.font_size / 3.,
BaselineShift::Top | BaselineShift::Bottom => 0.,
};
alignment + shift
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct InlineBoxPlacement {
pub(crate) aligned_subtree_root: u16,
pub(crate) baseline_offset: f32,
pub(crate) ascent: f32,
pub(crate) descent: f32,
}
pub(crate) fn inline_box_placement(
inline_box: &InlineBox,
parent_style: u16,
style_metrics: &[StyleMetrics],
quantize: bool,
) -> InlineBoxPlacement {
let ascent = inline_box.baseline.unwrap_or(inline_box.height);
let descent = inline_box.height - ascent;
let parent = style_metrics
.get(usize::from(parent_style))
.copied()
.unwrap_or_default();
let shift = shift_from_parent(inline_box.vertical_align, ascent, descent, &parent);
let baseline_offset = parent.exact_baseline_offset + shift;
InlineBoxPlacement {
aligned_subtree_root: parent.aligned_subtree_root,
baseline_offset: if quantize {
baseline_offset.round()
} else {
baseline_offset
},
ascent,
descent,
}
}
fn first_available_font<B: Brush>(
query: &mut Query<'_>,
rcx: &ResolveContext,
style: &ResolvedStyle<B>,
) -> Option<FontInstance> {
let families = rcx.stack(style.font_family).unwrap_or(&[]);
query.set_families(families.iter().copied().map(QueryFamily::Id));
query.set_attributes(fontique::Attributes {
width: style.font_width,
weight: style.font_weight,
style: style.font_style,
});
let font = query.first_available_font()?;
Some(FontInstance {
font: FontData {
data: font.blob,
index: font.index,
},
synthesis: font.synthesis,
})
}