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}