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
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(any(feature = "std", feature = "fixed", feature = "libm")))]
compile_error! { "Either the std, fixed, or libm feature must be enabled" }

extern crate alloc;
use alloc::vec::Vec;

mod first_fit;
pub use first_fit::*;

mod knuth_plass;
pub use knuth_plass::*;

mod math;
pub use math::{Fixed, Num};

/// A single item in a paragraph.
#[derive(Debug)]
pub enum Item<Box = (), Glue = (), Penalty = (), N = f32> {
    /// An unbreakable box containing paragraph content. Typically represents a glyph or sequence
    /// of glyphs. Lines may not be broken at boxes.
    Box {
        /// The width of the box.
        width: N,
        /// The box's data.
        data: Box,
    },
    /// Whitespace that separates boxes. Lines may be broken at glue items.
    Glue {
        /// The normal width of the whitespace.
        width: N,
        /// The stretch parameter. If this item needs to be stretched in order to lay out a line,
        /// the stretch amount will be proportional to this value.
        stretch: N,
        /// The shrink parameter. If this item needs to be shrunk in order to lay out a line, the
        /// shrink amount will be proportional to this value.
        shrink: N,
        /// The glue's data.
        data: Glue,
    },
    /// A penalty item. Represents a possible breakpoint with a particular aesthetic cost that
    /// indicates the desirability or undesirability of such a breakpoint.
    Penalty {
        /// The width of the penalty item.
        width: N,
        /// The aesthetic cost of the penalty item. A high cost is a relatively undesirable
        /// breakpoint, while a low cost indicates a relatively desirable breakpoint.
        cost: N,
        /// Whether or not this is a flagged penalty item. Some algorithms will attempt to avoid
        /// having multiple consecutive breaks at flagged penalty items.
        flagged: bool,
        /// The penalty's data.
        data: Penalty,
    },
}

impl<Box, Glue, Penalty, N: Num> Item<Box, Glue, Penalty, N> {
    fn penalty_cost(&self) -> N {
        match self {
            Item::Penalty { cost, .. } => *cost,
            _ => N::from(0i16),
        }
    }

    fn penalty_flag(&self) -> N {
        match self {
            Item::Penalty { flagged, .. } => {
                if *flagged {
                    N::from(1i16)
                } else {
                    N::from(0i16)
                }
            }
            _ => N::from(0i16),
        }
    }

    fn is_mandatory_break(&self) -> bool {
        match self {
            Item::Penalty { cost, .. } => *cost == N::NEG_INFINITY,
            _ => false,
        }
    }

    /// Returns the width, stretch, and shrink of the node at b and indicates whether or not b is a
    /// legal break.
    fn is_legal_breakpoint(&self, pred: Option<&Self>) -> (N, N, N, bool) {
        match self {
            Item::Box { width, .. } => (*width, N::from(0), N::from(0), false),
            Item::Glue {
                width,
                stretch,
                shrink,
                ..
            } => (
                *width,
                *stretch,
                *shrink,
                matches!(pred, Some(Item::Box { .. })),
            ),
            Item::Penalty { width, cost, .. } => {
                (*width, N::from(0), N::from(0), *cost != N::INFINITY)
            }
        }
    }

    /// Calculates the adjustment ratio for a break at the given item. Width, stretch, and shrink
    /// are for the line that ends at the break.
    fn adjustment_ratio(&self, width: N, stretch: N, shrink: N, line_width: N) -> N {
        let penalty_width = if let Item::Penalty { width, .. } = self {
            *width
        } else {
            N::from(0)
        };
        let width = width + penalty_width;
        if width < line_width {
            if stretch > N::from(0) {
                (line_width - width) / stretch
            } else {
                N::INFINITY
            }
        } else if width > line_width {
            if shrink > N::from(0) {
                (line_width - width) / shrink
            } else {
                N::NEG_INFINITY
            }
        } else {
            N::from(0)
        }
    }
}

/// A single line of text as represented by its break point and adjustment ratio.
#[derive(Debug, Default, Clone, Copy)]
pub struct Line<N: Num = f32> {
    /// The index of the item at which to break this line.
    pub break_at: usize,
    /// The adjustment ratio that should be applied to glue when rendering this line. If the
    /// adjustment ratio is negative, glue should be adjusted by its shrink parameter. If the
    /// adjustment ratio is positive, glue should be adjusted by its stretch parameter. In general,
    pub adjustment_ratio: N,
}

impl<N: Num> Line<N> {
    /// Returns the width of a glue item with the given width, stretch, and shrink once the
    /// adjustment ratio is taken into account.
    pub fn glue_width(&self, width: N, stretch: N, shrink: N) -> N {
        if self.adjustment_ratio < N::from(0i16) {
            width + shrink * self.adjustment_ratio
        } else if self.adjustment_ratio > N::from(0i16) {
            width + stretch * self.adjustment_ratio
        } else {
            width
        }
    }
}

/// Represents a paragraph layout algorithm
pub trait ParagraphLayout<Box = (), Glue = (), Penalty = (), N: Num = f32> {
    /// Lays out a paragraph with the given line width that consists of as list of items and
    /// returns the laid-out lines.
    fn layout_paragraph(
        &self,
        items: &[Item<Box, Glue, Penalty, N>],
        line_width: N,
    ) -> Vec<Line<N>>;
}