Skip to main content

fleuron/style/properties/
exclusion.rs

1//! A block placed against the page rather than in the flow, and the
2//! contour the prose around it keeps clear of.
3
4use serde::Serialize;
5
6use super::edges::Edges;
7use super::value::Length;
8
9/// Whether an element sits in the flow or against the page.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
11#[serde(rename_all = "snake_case")]
12pub enum Position {
13    /// `static`: the element sits where the flow puts it.
14    Static,
15    /// `relative`: the element keeps its place in the flow, and the
16    /// engine draws it moved by its insets. Nothing around it moves.
17    Relative,
18    /// `absolute`: the element comes out of the flow and sits against
19    /// the page area, at the insets it declares.
20    Absolute,
21}
22
23/// How far one edge of a positioned box sits from the matching edge of
24/// the page area, from `top`, `right`, `bottom` and `left`.
25#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
26#[serde(rename_all = "snake_case")]
27pub enum Inset {
28    /// `auto`: the opposite edge places the box. Where that edge is
29    /// `auto` as well, the box sits at the edge of the page area.
30    Auto,
31    /// A length in points, negative where the box reaches into the
32    /// margin.
33    Points(f32),
34    /// A percentage of the page area's width, for `left` and `right`,
35    /// or of its height, for `top` and `bottom`. It stays a percentage
36    /// until layout resolves it against the page area.
37    Percent(f32),
38}
39
40impl Inset {
41    /// The inset in points across a page area `extent` wide or tall,
42    /// or `None` where it is `auto`.
43    pub fn resolve(self, extent: f32) -> Option<f32> {
44        match self {
45            Inset::Auto => None,
46            Inset::Points(points) => Some(points),
47            Inset::Percent(percent) => Some(percent / 100.0 * extent),
48        }
49    }
50}
51
52impl Edges<Inset> {
53    /// How far `position: relative` moves a box across and down a page
54    /// area of `size`. `left` outranks `right` and `top` outranks
55    /// `bottom`, as in CSS.
56    pub fn offset(self, (width, height): (f32, f32)) -> (f32, f32) {
57        let axis = |start: Inset, end: Inset, extent: f32| match (
58            start.resolve(extent),
59            end.resolve(extent),
60        ) {
61            (Some(start), _) => start,
62            (None, Some(end)) => -end,
63            (None, None) => 0.0,
64        };
65        (
66            axis(self.left, self.right, width),
67            axis(self.top, self.bottom, height),
68        )
69    }
70}
71
72/// Which side of an exclusion the prose sets on, from `wrap-flow`. An
73/// exclusion is a box that the text keeps clear of.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
75#[serde(rename_all = "kebab-case")]
76pub enum WrapFlow {
77    /// `auto`: the box excludes nothing, and the text runs under it.
78    Auto,
79    /// `both`: the prose sets on either side of it.
80    Both,
81    /// `start`: the prose sets on the side the line starts at.
82    Start,
83    /// `end`: the prose sets on the side the line ends at.
84    End,
85}
86
87/// `shape-outside` as the sheet wrote it, before the cascade knows
88/// the font size its lengths are relative to.
89#[derive(Debug, Clone, PartialEq)]
90pub enum ShapeSource {
91    /// `none`.
92    None,
93    /// `auto`.
94    Auto,
95    /// `polygon(…)`, as pairs of written lengths.
96    Polygon(Vec<(Length, Length)>),
97}
98
99/// The contour prose sets around, from `shape-outside`.
100///
101/// A contour is the outline the text keeps clear of, in place of the
102/// box.
103#[derive(Debug, Clone, PartialEq, Serialize)]
104#[serde(rename_all = "snake_case")]
105pub enum ShapeOutside {
106    /// `none`: the prose keeps clear of the box itself.
107    None,
108    /// `auto`: the contour comes from the image's own alpha channel.
109    /// A format that carries no alpha contributes its box.
110    Auto,
111    /// `polygon(…)`: the points the sheet wrote, read against the
112    /// margin box.
113    Polygon(Vec<ShapePoint>),
114}
115
116/// One point of a `polygon()`, from the top left of the margin box.
117#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
118pub struct ShapePoint {
119    /// Across the box.
120    pub x: Coord,
121    /// Down the box.
122    pub y: Coord,
123}
124
125/// One coordinate of a shape: a length, or a fraction of the box the
126/// shape is read against.
127///
128/// `em` and `rem` are points by the time the cascade is done. A
129/// percentage is not, because the box it measures against is the
130/// image's, and the image is sized in the layout pass.
131#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
132#[serde(rename_all = "snake_case")]
133pub enum Coord {
134    /// An absolute length in points.
135    Points(f32),
136    /// A percentage of the box's own width or height.
137    Percent(f32),
138}
139
140impl Coord {
141    /// What the cascade makes of one written length: `em` and `rem`
142    /// against the font size in force, a percentage kept as one.
143    pub fn of(length: Length, size: f32, root: f32) -> Coord {
144        match length {
145            Length::Percent(percent) => Coord::Percent(percent),
146            other => Coord::Points(other.to_points(size, root)),
147        }
148    }
149
150    /// The coordinate in points, across a box `extent` wide or tall.
151    pub fn to_points(self, extent: f32) -> f32 {
152        match self {
153            Coord::Points(points) => points,
154            Coord::Percent(percent) => percent / 100.0 * extent,
155        }
156    }
157}