text_layout/
first_fit.rs

1extern crate alloc;
2use alloc::vec::Vec;
3
4use crate::math::Num;
5use crate::{Item, Line, ParagraphLayout};
6
7/// Runs the first-fit line-breaking algorithm to calculate the break points for a paragraph.
8pub struct FirstFit<N> {
9    threshold: N,
10    allow_overflow: bool,
11}
12
13impl<N: Num> FirstFit<N> {
14    /// Creates a new FirstFit layout with default parameter values.
15    pub fn new() -> Self {
16        FirstFit {
17            threshold: N::from(1),
18            allow_overflow: false,
19        }
20    }
21
22    /// Sets the adjustment ratio threshold. Lines will not be allowed to break at a given point if
23    /// doing so would cause the line's adjustment ratio to exceed this value. Defaults to 1.
24    pub fn with_threshold(mut self, threshold: N) -> Self {
25        self.threshold = threshold;
26        self
27    }
28
29    /// Configures the layout to allow lines that exceed the maximum line with if the layout would
30    /// fail otherwise.
31    pub fn allow_overflow(mut self, allow_overflow: bool) -> Self {
32        self.allow_overflow = allow_overflow;
33        self
34    }
35}
36
37impl<N: Num> Default for FirstFit<N> {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl<Box, Glue, Penalty, N: Num> ParagraphLayout<Box, Glue, Penalty, N> for FirstFit<N> {
44    fn layout_paragraph(
45        &self,
46        items: &[Item<Box, Glue, Penalty, N>],
47        line_width: N,
48    ) -> Vec<Line<N>> {
49        let l = FirstFitLayout {
50            line_width,
51            threshold: self.threshold,
52            allow_overflow: self.allow_overflow,
53            width: N::from(0),
54            stretch: N::from(0),
55            shrink: N::from(0),
56            lines: Vec::new(),
57        };
58        l.layout_paragraph(items)
59    }
60}
61
62struct Break<N> {
63    width: N,
64    stretch: N,
65    shrink: N,
66    adjustment_ratio: N,
67    is_mandatory: bool,
68    at: usize,
69}
70
71struct FirstFitLayout<N: Num> {
72    line_width: N,
73
74    threshold: N,
75    allow_overflow: bool,
76
77    width: N,
78    stretch: N,
79    shrink: N,
80
81    lines: Vec<Line<N>>,
82}
83
84impl<N: Num> FirstFitLayout<N> {
85    fn break_at(&mut self, b: Break<N>) {
86        self.lines.push(Line {
87            break_at: b.at,
88            adjustment_ratio: b.adjustment_ratio,
89        });
90
91        self.width -= b.width;
92        self.stretch -= b.stretch;
93        self.shrink -= b.shrink;
94    }
95
96    fn layout_paragraph<Box, Glue, Penalty>(
97        mut self,
98        items: &[Item<Box, Glue, Penalty, N>],
99    ) -> Vec<Line<N>> {
100        let mut last_breakpoint: Option<Break<N>> = None;
101        for (b, item) in items.iter().enumerate() {
102            let (width, stretch, shrink, is_legal) =
103                item.is_legal_breakpoint((b != 0).then(|| &items[b - 1]));
104            if is_legal {
105                let adjustment_ratio =
106                    item.adjustment_ratio(self.width, self.stretch, self.shrink, self.line_width);
107                if let Some(b) = last_breakpoint {
108                    if adjustment_ratio < N::from(-1)
109                        || adjustment_ratio > self.threshold
110                        || b.is_mandatory
111                    {
112                        self.break_at(b);
113                    }
114                }
115
116                let adjustment_ratio =
117                    item.adjustment_ratio(self.width, self.stretch, self.shrink, self.line_width);
118
119                let adjustment_ratio = if adjustment_ratio < N::from(-1) {
120                    if !self.allow_overflow {
121                        return Vec::new();
122                    }
123                    N::from(0)
124                } else {
125                    adjustment_ratio
126                };
127                if adjustment_ratio > self.threshold {
128                    return Vec::new();
129                }
130
131                last_breakpoint = Some(Break {
132                    width: self.width,
133                    stretch: self.stretch,
134                    shrink: self.shrink,
135                    adjustment_ratio,
136                    is_mandatory: item.is_mandatory_break(),
137                    at: b,
138                });
139            }
140
141            self.width += width;
142            self.stretch += stretch;
143            self.shrink += shrink;
144        }
145        if let Some(b) = last_breakpoint {
146            self.break_at(b);
147        }
148
149        self.lines
150    }
151}