1#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Span {
12 pub origin: f32,
14 pub width: f32,
16 pub ends_band: bool,
18}
19
20impl Span {
21 pub fn band(origin: f32, width: f32) -> Span {
23 Span {
24 origin,
25 width,
26 ends_band: true,
27 }
28 }
29
30 pub fn ending(end: f32, width: f32) -> Span {
34 Span::band(end - width, width)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
47pub struct Measure {
48 leading: Vec<Span>,
49 rest: Span,
50}
51
52impl Measure {
53 pub fn uniform(points: f32) -> Measure {
55 Measure {
56 leading: Vec::new(),
57 rest: Span::band(0.0, points),
58 }
59 }
60
61 pub fn new(leading: Vec<Span>, rest: Span) -> Measure {
64 Measure {
65 leading,
66 rest: Span::band(rest.origin, rest.width),
67 }
68 }
69
70 pub fn rest(&self) -> Span {
72 self.rest
73 }
74
75 pub fn at(&self, index: usize) -> Span {
77 self.leading.get(index).copied().unwrap_or(self.rest)
78 }
79
80 pub(super) fn settled(&self) -> usize {
84 self.leading.len()
85 }
86}
87
88impl From<f32> for Measure {
89 fn from(points: f32) -> Measure {
90 Measure::uniform(points)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::content::{Attributes, Inline, NodeId};
97 use crate::lines::testing::{
98 OPENING, body, divided_band, justified, line_text, one_run, registry, span_text,
99 span_width_pt, units_per_em,
100 };
101 use crate::lines::{Line, LineBreakOptions, LineLayout, Measure, Span};
102
103 #[test]
107 fn a_band_of_two_spans_sets_text_in_both() {
108 let layout = LineLayout::new(registry());
109 let lines = layout.layout(
110 &one_run(OPENING),
111 &body(),
112 divided_band(80.0, 20.0),
113 LineBreakOptions::default(),
114 );
115 let first = &lines[0];
116 assert_eq!(first.spans.len(), 2, "the band was set in one span");
117 assert!(
118 !span_text(first, 0).is_empty() && !span_text(first, 1).is_empty(),
119 "a span of the band holds no text: {first:?}"
120 );
121 assert_eq!(
122 OPENING
123 .replace(' ', "")
124 .find(&span_text(first, 1).replace(' ', "")),
125 Some(span_text(first, 0).replace(' ', "").len()),
126 "the second span does not carry on from the first"
127 );
128 assert_eq!(first.spans[1].offset, 100.0);
131 assert!(
132 lines[1..].iter().all(|line| line.spans.len() == 1),
133 "a band under the divided one was set in more than one span"
134 );
135 }
136
137 #[test]
141 fn justification_flushes_an_interior_span() {
142 let layout = LineLayout::new(registry());
143 let lines = layout.layout(
144 &one_run(OPENING),
145 &body(),
146 divided_band(80.0, 20.0),
147 justified(),
148 );
149 assert!(
150 (span_width_pt(&lines[0], 0) - 80.0).abs() < 0.01,
151 "the first span of the band is {}pt of 80pt",
152 span_width_pt(&lines[0], 0),
153 );
154 }
155
156 #[test]
159 fn a_shortened_measure_only_holds_for_the_lines_it_names() {
160 let text = "one two three four five six seven eight nine ten eleven twelve";
161 let measure = Measure::new(vec![Span::band(80.0, 40.0); 2], Span::band(0.0, 120.0));
162 let layout = LineLayout::new(registry());
163 let inlines = vec![Inline::Text {
164 id: NodeId::UNASSIGNED,
165 value: text.to_string(),
166 attributes: Attributes::default(),
167 position: None,
168 span: None,
169 }];
170 let lines = layout.layout(&inlines, &body(), measure.clone(), Default::default());
171 assert!(lines.len() > 3, "expected several lines: {lines:?}");
172 let width_pt = |line: &Line| line.width as f32 / units_per_em() as f32 * body().size;
173 for (index, line) in lines.iter().enumerate() {
174 let allowed = measure.at(index).width;
175 assert!(
176 width_pt(line) <= allowed,
177 "line {index} is {}pt against a measure of {allowed}pt",
178 width_pt(line),
179 );
180 }
181 assert!(
185 width_pt(&lines[2]) > measure.at(0).width,
186 "the measure never widened"
187 );
188 let uniform = layout.layout(&inlines, &body(), 120.0, Default::default());
189 assert!(uniform.len() < lines.len());
190 assert_eq!(
191 lines.iter().map(line_text).collect::<Vec<_>>().join(" "),
192 text,
193 );
194 }
195}