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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
|
//! The line layouter arranges boxes into lines.
//!
//! Along the primary axis, the boxes are laid out next to each other while they
//! fit into a line. When a line break is necessary, the line is finished and a
//! new line is started offset on the secondary axis by the height of previous
//! line and the extra line spacing.
//!
//! Internally, the line layouter uses a stack layouter to arrange the finished
//! lines.
use super::stack::{StackLayouter, StackContext};
use super::*;
/// Performs the line layouting.
#[derive(Debug)]
pub struct LineLayouter {
/// The context for layouting.
ctx: LineContext,
/// The underlying stack layouter.
stack: StackLayouter,
/// The currently written line.
run: LineRun,
}
/// The context for line layouting.
#[derive(Debug, Clone)]
pub struct LineContext {
/// The spaces to layout in.
pub spaces: LayoutSpaces,
/// The initial layouting axes, which can be updated by the
/// [`LineLayouter::set_axes`] method.
pub axes: LayoutAxes,
/// Which alignment to set on the resulting layout. This affects how it will
/// be positioned in a parent box.
pub alignment: LayoutAlignment,
/// Whether to have repeated spaces or to use only the first and only once.
pub repeat: bool,
/// Whether to output a command which renders a debugging box showing the
/// extent of the layout.
pub debug: bool,
/// The line spacing.
pub line_spacing: Length,
}
/// A line run is a sequence of boxes with the same alignment that are arranged
/// in a line. A real line can consist of multiple runs with different
/// alignments.
#[derive(Debug)]
struct LineRun {
/// The so-far accumulated layouts in the line.
layouts: Vec<(Length, Layout)>,
/// The width (primary length) and maximal height (secondary length) of the
/// line.
size: Size,
/// The alignment of all layouts in the line.
///
/// When a new run is created the alignment is yet to be determined. Once a
/// layout is added, it is decided which alignment the run has and all
/// further elements of the run must have this alignment.
alignment: Option<LayoutAlignment>,
/// If another line run with different alignment already took up some space
/// of the line, this run has less space and how much is stored here.
usable: Option<Length>,
/// A possibly cached soft spacing or spacing state.
last_spacing: LastSpacing,
}
impl LineLayouter {
/// Create a new line layouter.
pub fn new(ctx: LineContext) -> LineLayouter {
LineLayouter {
stack: StackLayouter::new(StackContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
}),
ctx,
run: LineRun::new(),
}
}
/// Add a layout to the run.
pub fn add(&mut self, layout: Layout) {
let axes = self.ctx.axes;
if let Some(alignment) = self.run.alignment {
if layout.alignment.secondary != alignment.secondary {
// TODO: Issue warning for non-fitting alignment in
// non-repeating context.
let fitting = self.stack.is_fitting_alignment(layout.alignment);
if !fitting && self.ctx.repeat {
self.finish_space(true);
} else {
self.finish_line();
}
} else if layout.alignment.primary < alignment.primary {
self.finish_line();
} else if layout.alignment.primary > alignment.primary {
let mut rest_run = LineRun::new();
let usable = self.stack.usable().primary(axes);
rest_run.usable = Some(match layout.alignment.primary {
Alignment::Origin => unreachable!("origin > x"),
Alignment::Center => usable - 2 * self.run.size.x,
Alignment::End => usable - self.run.size.x,
});
rest_run.size.y = self.run.size.y;
self.finish_line();
self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
self.run = rest_run;
}
}
if let LastSpacing::Soft(spacing, _) = self.run.last_spacing {
self.add_primary_spacing(spacing, SpacingKind::Hard);
}
let size = layout.dimensions.generalized(axes);
if !self.usable().fits(size) {
if !self.line_is_empty() {
self.finish_line();
}
// TODO: Issue warning about overflow if there is overflow.
if !self.usable().fits(size) {
self.stack.skip_to_fitting_space(layout.dimensions);
}
}
self.run.alignment = Some(layout.alignment);
self.run.layouts.push((self.run.size.x, layout));
self.run.size.x += size.x;
self.run.size.y.max_eq(size.y);
self.run.last_spacing = LastSpacing::None;
}
/// Add multiple layouts to the run.
///
/// This function simply calls `add` repeatedly for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
self.add(layout);
}
}
/// The remaining usable size of the run.
///
/// This specifies how much more fits before a line break needs to be
/// issued.
fn usable(&self) -> Size {
// The base is the usable space per stack layouter.
let mut usable = self.stack.usable().generalized(self.ctx.axes);
// If this is a alignment-continuing line, we override the primary
// usable size.
if let Some(primary) = self.run.usable {
usable.x = primary;
}
usable.x -= self.run.size.x;
usable
}
/// Add spacing along the primary axis to the line.
pub fn add_primary_spacing(&mut self, mut spacing: Length, kind: SpacingKind) {
match kind {
// A hard space is simply an empty box.
SpacingKind::Hard => {
spacing.min_eq(self.usable().x);
self.run.size.x += spacing;
self.run.last_spacing = LastSpacing::Hard;
}
// A soft space is cached if it is not consumed by a hard space or
// previous soft space with higher level.
SpacingKind::Soft(level) => {
let consumes = match self.run.last_spacing {
LastSpacing::None => true,
LastSpacing::Soft(_, prev) if level < prev => true,
_ => false,
};
if consumes {
self.run.last_spacing = LastSpacing::Soft(spacing, level);
}
}
}
}
/// Finish the line and add secondary spacing to the underlying stack.
pub fn add_secondary_spacing(&mut self, spacing: Length, kind: SpacingKind) {
self.finish_line_if_not_empty();
self.stack.add_spacing(spacing, kind)
}
/// Update the layouting axes used by this layouter.
pub fn set_axes(&mut self, axes: LayoutAxes) {
self.finish_line_if_not_empty();
self.ctx.axes = axes;
self.stack.set_axes(axes)
}
/// Update the layouting spaces to use.
///
/// If `replace_empty` is true, the current space is replaced if there are
/// no boxes laid into it yet. Otherwise, only the followup spaces are
/// replaced.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
}
/// Update the line spacing.
pub fn set_line_spacing(&mut self, line_spacing: Length) {
self.ctx.line_spacing = line_spacing;
}
/// The remaining inner layout spaces. Inner means, that padding is already
/// subtracted and the spaces are unexpanding. This can be used to signal
/// a function how much space it has to layout itself.
pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = self.stack.remaining();
*spaces[0].dimensions.secondary_mut(self.ctx.axes)
-= self.run.size.y;
spaces
}
/// Whether the currently set line is empty.
pub fn line_is_empty(&self) -> bool {
self.run.size == Size::ZERO && self.run.layouts.is_empty()
}
/// Finish the last line and compute the final list of boxes.
pub fn finish(mut self) -> MultiLayout {
self.finish_line_if_not_empty();
self.stack.finish()
}
/// Finish the currently active space and start a new one.
///
/// At the top level, this is a page break.
pub fn finish_space(&mut self, hard: bool) {
self.finish_line_if_not_empty();
self.stack.finish_space(hard)
}
/// Finish the line and start a new one.
pub fn finish_line(&mut self) {
let mut actions = LayoutActions::new();
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
for (offset, layout) in layouts {
let x = match self.ctx.axes.primary.is_positive() {
true => offset,
false => self.run.size.x
- offset
- layout.dimensions.primary(self.ctx.axes),
};
let pos = Size::with_x(x);
actions.add_layout(pos, layout);
}
self.stack.add(Layout {
dimensions: self.run.size.specialized(self.ctx.axes),
alignment: self.run.alignment
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
actions: actions.into_vec(),
});
self.run = LineRun::new();
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
}
/// Finish the current line if it is not empty.
fn finish_line_if_not_empty(&mut self) {
if !self.line_is_empty() {
self.finish_line()
}
}
}
impl LineRun {
fn new() -> LineRun {
LineRun {
layouts: vec![],
size: Size::ZERO,
alignment: None,
usable: None,
last_spacing: LastSpacing::Hard,
}
}
}
|