summaryrefslogtreecommitdiff
path: root/src/layout/mod.rs
blob: 1ee862e3b2d514498f7fcdeb346a84d619a52d8f (plain) (blame)
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
//! Layouting of syntax trees into box layouts.

pub mod primitive;

mod elements;
mod line;
mod stack;
mod tree;

pub use elements::*;
pub use line::*;
pub use primitive::*;
pub use stack::*;
pub use tree::*;

use crate::geom::{Insets, Point, Rect, RectExt, Sides, Size, SizeExt};

use crate::eval::{PageState, State, TextState};
use crate::font::SharedFontLoader;
use crate::syntax::SynTree;
use crate::{Feedback, Pass};

/// Layout a syntax tree and return the produced layout.
pub async fn layout(
    tree: &SynTree,
    state: State,
    loader: SharedFontLoader,
) -> Pass<MultiLayout> {
    let space = LayoutSpace {
        size: state.page.size,
        insets: state.page.insets(),
        expansion: LayoutExpansion::new(true, true),
    };

    let constraints = LayoutConstraints {
        root: true,
        base: space.usable(),
        spaces: vec![space],
        repeat: true,
    };

    let mut ctx = LayoutContext {
        loader,
        state,
        constraints,
        f: Feedback::new(),
    };

    let layouts = layout_tree(&tree, &mut ctx).await;

    Pass::new(layouts, ctx.f)
}

/// A collection of layouts.
pub type MultiLayout = Vec<BoxLayout>;

/// A finished box with content at fixed positions.
#[derive(Debug, Clone, PartialEq)]
pub struct BoxLayout {
    /// The size of the box.
    pub size: Size,
    /// How to align this box in a parent container.
    pub align: LayoutAlign,
    /// The elements composing this layout.
    pub elements: LayoutElements,
}

/// The context for layouting.
#[derive(Debug, Clone)]
pub struct LayoutContext {
    /// The font loader to query fonts from when typesetting text.
    pub loader: SharedFontLoader,
    /// The active state.
    pub state: State,
    /// The active constraints.
    pub constraints: LayoutConstraints,
    /// The accumulated feedback.
    pub f: Feedback,
}

/// The constraints for layouting a single node.
#[derive(Debug, Clone)]
pub struct LayoutConstraints {
    /// Whether this layouting process is the root page-building process.
    pub root: bool,
    /// The unpadded size of this container (the base 100% for relative sizes).
    pub base: Size,
    /// The spaces to layout into.
    pub spaces: LayoutSpaces,
    /// Whether to spill over into copies of the last space or finish layouting
    /// when the last space is used up.
    pub repeat: bool,
}

/// A collection of layout spaces.
pub type LayoutSpaces = Vec<LayoutSpace>;

/// The space into which content is laid out.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct LayoutSpace {
    /// The maximum size of the rectangle to layout into.
    pub size: Size,
    /// Padding that should be respected on each side.
    pub insets: Insets,
    /// Whether to expand the size of the resulting layout to the full size of
    /// this space or to shrink it to fit the content.
    pub expansion: LayoutExpansion,
}

impl LayoutSpace {
    /// The position of the padded start in the space.
    pub fn start(&self) -> Point {
        Point::new(-self.insets.x0, -self.insets.y0)
    }

    /// The actually usable area (size minus padding).
    pub fn usable(&self) -> Size {
        self.size + self.insets.size()
    }

    /// The inner layout space with size reduced by the padding, zero padding of
    /// its own and no layout expansion.
    pub fn inner(&self) -> Self {
        Self {
            size: self.usable(),
            insets: Insets::ZERO,
            expansion: LayoutExpansion::new(false, false),
        }
    }
}

/// A sequence of layouting commands.
pub type Commands = Vec<Command>;

/// Commands executable by the layouting engine.
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
    /// Layout the given tree in the current context (i.e. not nested). The
    /// content of the tree is not laid out into a separate box and then added,
    /// but simply laid out flatly in the active layouting process.
    ///
    /// This has the effect that the content fits nicely into the active line
    /// layouting, enabling functions to e.g. change the style of some piece of
    /// text while keeping it part of the current paragraph.
    LayoutSyntaxTree(SynTree),

    /// Add a finished layout.
    Add(BoxLayout),
    /// Add multiple layouts, one after another. This is equivalent to multiple
    /// `Add` commands.
    AddMultiple(MultiLayout),

    /// Add spacing of the given kind along the primary or secondary axis. The
    /// kind defines how the spacing interacts with surrounding spacing.
    AddSpacing(f64, SpacingKind, GenAxis),

    /// Start a new line.
    BreakLine,
    /// Start a new page, which will be part of the finished layout even if it
    /// stays empty (since the page break is a _hard_ space break).
    BreakPage,

    /// Update the text style.
    SetTextState(TextState),
    /// Update the page style.
    SetPageState(PageState),

    /// Update the alignment for future boxes added to this layouting process.
    SetAlignment(LayoutAlign),
    /// Update the layouting system along which future boxes will be laid
    /// out. This ends the current line.
    SetSystem(LayoutSystem),
}

/// Defines how spacing interacts with surrounding spacing.
///
/// There are two options for interaction: Hard and soft spacing. Typically,
/// hard spacing is used when a fixed amount of space needs to be inserted no
/// matter what. In contrast, soft spacing can be used to insert a default
/// spacing between e.g. two words or paragraphs that can still be overridden by
/// a hard space.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SpacingKind {
    /// Hard spaces are always laid out and consume surrounding soft space.
    Hard,
    /// Soft spaces are not laid out if they are touching a hard space and
    /// consume neighbouring soft spaces with higher levels.
    Soft(u32),
}

impl SpacingKind {
    /// The standard spacing kind used for paragraph spacing.
    pub const PARAGRAPH: Self = Self::Soft(1);

    /// The standard spacing kind used for line spacing.
    pub const LINE: Self = Self::Soft(2);

    /// The standard spacing kind used for word spacing.
    pub const WORD: Self = Self::Soft(1);
}

/// The spacing kind of the most recently inserted item in a layouting process.
///
/// Since the last inserted item may not be spacing at all, this can be `None`.
#[derive(Debug, Copy, Clone, PartialEq)]
enum LastSpacing {
    /// The last item was hard spacing.
    Hard,
    /// The last item was soft spacing with the given width and level.
    Soft(f64, u32),
    /// The last item wasn't spacing.
    None,
}

impl LastSpacing {
    /// The width of the soft space if this is a soft space or zero otherwise.
    fn soft_or_zero(self) -> f64 {
        match self {
            LastSpacing::Soft(space, _) => space,
            _ => 0.0,
        }
    }
}