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>>;
}