use super::{
BreakReason,
data::{ClusterData, LineItemData},
};
use crate::data::LayoutData;
use crate::style::Brush;
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Alignment {
#[default]
Start,
End,
Left,
Center,
Right,
Justify,
}
#[derive(Debug, Clone, Copy)]
pub struct AlignmentOptions {
pub align_when_overflowing: bool,
}
#[expect(
clippy::derivable_impls,
reason = "Make default values explicit rather than relying on the implicit default value of bool"
)]
impl Default for AlignmentOptions {
fn default() -> Self {
Self {
align_when_overflowing: false,
}
}
}
pub(crate) fn align<B: Brush>(
layout: &mut LayoutData<B>,
alignment: Alignment,
options: AlignmentOptions,
) {
#[cfg(feature = "accesskit")]
{
layout.alignment = Some(alignment);
}
layout.is_aligned_justified = alignment == Alignment::Justify;
align_impl::<_, false>(layout, alignment, options);
}
pub(crate) fn unjustify<B: Brush>(layout: &mut LayoutData<B>) {
if layout.is_aligned_justified {
align_impl::<_, true>(layout, Alignment::Justify, AlignmentOptions::default());
layout.is_aligned_justified = false;
}
}
fn align_impl<B: Brush, const UNDO_JUSTIFICATION: bool>(
layout: &mut LayoutData<B>,
alignment: Alignment,
options: AlignmentOptions,
) {
let is_rtl = layout.base_level & 1 == 1;
for line in &mut layout.lines {
let indent = line.indent;
if is_rtl {
line.metrics.offset = -line.metrics.trailing_whitespace;
} else {
line.metrics.offset = indent;
}
let line_width = line.metrics.inline_max_coord - line.metrics.inline_min_coord;
let free_space =
line_width - indent - line.metrics.advance + line.metrics.trailing_whitespace;
if !options.align_when_overflowing && free_space <= 0.0 {
if is_rtl {
line.metrics.offset += free_space;
}
continue;
}
match (alignment, is_rtl) {
(Alignment::Left, _) | (Alignment::Start, false) | (Alignment::End, true) => {
}
(Alignment::Right, _) | (Alignment::Start, true) | (Alignment::End, false) => {
line.metrics.offset += free_space;
}
(Alignment::Center, _) => {
line.metrics.offset += free_space * 0.5;
}
(Alignment::Justify, _) => {
if free_space <= 0.0 {
continue;
}
if matches!(line.break_reason, BreakReason::None | BreakReason::Explicit)
|| line.num_spaces == 0
{
if is_rtl {
line.metrics.offset += free_space;
}
continue;
}
let adjustment =
free_space / line.num_spaces as f32 * if UNDO_JUSTIFICATION { -1. } else { 1. };
let mut applied = 0;
let line_items: &mut dyn Iterator<Item = &LineItemData> = if is_rtl {
&mut layout.line_items[line.item_range.clone()].iter().rev()
} else {
&mut layout.line_items[line.item_range.clone()].iter()
};
line_items
.filter(|item| item.is_text_run())
.for_each(|line_item| {
let clusters = &mut layout.clusters[line_item.cluster_range.clone()];
let line_item_is_rtl = line_item.bidi_level & 1 != 0;
let clusters: &mut dyn Iterator<Item = &mut ClusterData> =
if line_item_is_rtl {
&mut clusters.iter_mut().rev()
} else {
&mut clusters.iter_mut()
};
clusters.for_each(|cluster| {
if applied == line.num_spaces {
return;
}
if cluster.info.whitespace().is_space_or_nbsp() {
cluster.advance += adjustment;
applied += 1;
}
});
});
}
}
}
}