1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2024 the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use super::BreakReason;
use crate::data::LayoutData;
use crate::style::Brush;
/// Alignment of a layout.
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Alignment {
/// This is [`Alignment::Left`] for LTR text and [`Alignment::Right`] for RTL text.
#[default]
Start,
/// This is [`Alignment::Right`] for LTR text and [`Alignment::Left`] for RTL text.
End,
/// Align content to the left edge.
///
/// For alignment that should be aware of text direction, use [`Alignment::Start`] or
/// [`Alignment::End`] instead.
Left,
/// Align each line centered within the container.
Center,
/// Align content to the right edge.
///
/// For alignment that should be aware of text direction, use [`Alignment::Start`] or
/// [`Alignment::End`] instead.
Right,
/// Justify each line by spacing out content.
///
/// The last line of a paragraph is start-aligned instead, unless
/// [`AlignmentOptions::last_line_alignment`] is set.
Justify,
}
/// Additional options to fine tune alignment
#[derive(Debug, Clone, Copy)]
pub struct AlignmentOptions {
/// If set to `true`, "end" and "center" alignment will apply even if the line contents are
/// wider than the alignment width. If it is set to `false`, all overflowing lines will be
/// [`Alignment::Start`] aligned.
pub align_when_overflowing: bool,
/// The alignment of the last line of each paragraph: the last line of the layout and every
/// line that ends in an explicit line break. This corresponds to the CSS `text-align-last`
/// property.
///
/// If set to `None`, those lines use the alignment passed to [`Layout::align`], except that
/// [`Alignment::Justify`] falls back to [`Alignment::Start`]. This is the behavior of
/// `text-align-last: auto`.
///
/// [`Layout::align`]: crate::Layout::align
pub last_line_alignment: Option<Alignment>,
}
#[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,
last_line_alignment: None,
}
}
}
/// Align the layout.
pub(crate) fn align<B: Brush>(
layout: &mut LayoutData<B>,
alignment: Alignment,
options: AlignmentOptions,
) {
layout.alignment = Some(alignment);
let is_rtl = layout.base_level.is_rtl();
// Apply alignment to line items
for line in &mut layout.lines {
line.justification.amount_per_opportunity = 0.;
let indent = line.metrics.indent;
if is_rtl {
// In RTL text, trailing whitespace is on the left. As we hang that whitespace, offset
// the line to the left. Note: indent is not subtracted here because `free_space` below
// already accounts for it.
line.metrics.offset = -line.metrics.hanging_advance;
} else {
line.metrics.offset = indent;
}
// Compute free space.
let line_width = line.metrics.inline_max_coord - line.metrics.inline_min_coord;
let free_space = line_width - indent - line.metrics.advance + line.metrics.hanging_advance;
if !options.align_when_overflowing && free_space <= 0.0 {
if is_rtl {
// In RTL text, right-align on overflow.
line.metrics.offset += free_space;
}
continue;
}
let is_last_line = matches!(line.break_reason, BreakReason::None | BreakReason::Explicit);
let line_alignment = if !is_last_line {
alignment
} else if let Some(last_line_alignment) = options.last_line_alignment {
last_line_alignment
} else if alignment == Alignment::Justify {
Alignment::Start
} else {
alignment
};
match (line_alignment, is_rtl) {
(Alignment::Left, _) | (Alignment::Start, false) | (Alignment::End, true) => {
// Do nothing
}
(Alignment::Right, _) | (Alignment::Start, true) | (Alignment::End, false) => {
line.metrics.offset += free_space;
}
(Alignment::Center, _) => {
line.metrics.offset += free_space * 0.5;
}
(Alignment::Justify, _) => {
// Justified alignment doesn't have any effect if free_space is negative or zero
if free_space <= 0.0 {
continue;
}
// Justified alignment doesn't apply if there are no whitespace gaps to adjust. In
// that case, start-align, i.e., left-align for LTR text and right-align for RTL
// text.
if line.num_justification_opportunities == 0 {
if is_rtl {
line.metrics.offset += free_space;
}
continue;
}
line.justification.amount_per_opportunity =
free_space / line.num_justification_opportunities as f32;
}
}
}
}