chromiumoxide/
layout.rs

1//! Code based on [rust-headless-chrome](https://github.com/atroche/rust-headless-chrome/blob/master/src/browser/tab/element/box_model.rs)
2
3use chromiumoxide_cdp::cdp::browser_protocol::dom::Quad;
4use chromiumoxide_cdp::cdp::browser_protocol::input::{
5    DispatchMouseEventParams, DispatchMouseEventType, MouseButton,
6};
7use chromiumoxide_cdp::cdp::browser_protocol::page::Viewport;
8
9#[derive(Debug, Copy, Clone, PartialEq)]
10pub struct Point {
11    pub x: f64,
12    pub y: f64,
13}
14
15impl Point {
16    /// Create a new Point instance
17    pub fn new(x: f64, y: f64) -> Self {
18        Self { x, y }
19    }
20
21    fn area(&self, other: &Self) -> f64 {
22        (self.x * other.y - other.x * self.y) / 2.
23    }
24}
25
26impl std::ops::Add<Point> for Point {
27    type Output = Self;
28
29    fn add(self, other: Self) -> Self {
30        Self {
31            x: self.x + other.x,
32            y: self.y + other.y,
33        }
34    }
35}
36
37impl std::ops::Sub<Point> for Point {
38    type Output = Self;
39
40    fn sub(self, other: Self) -> Self {
41        Self {
42            x: self.x - other.x,
43            y: self.y - other.y,
44        }
45    }
46}
47
48impl std::ops::Div<f64> for Point {
49    type Output = Self;
50
51    fn div(self, other: f64) -> Self {
52        Self {
53            x: self.x / other,
54            y: self.y / other,
55        }
56    }
57}
58
59/// Converts a point into Left-Down-Single-Mouseclick
60impl From<Point> for DispatchMouseEventParams {
61    fn from(el: Point) -> DispatchMouseEventParams {
62        let mut params =
63            DispatchMouseEventParams::new(DispatchMouseEventType::MousePressed, el.x, el.y);
64        params.button = Some(MouseButton::Left);
65        params.click_count = Some(1);
66        params
67    }
68}
69
70#[derive(Debug, Copy, Clone)]
71pub struct ElementQuad {
72    pub top_left: Point,
73    pub top_right: Point,
74    pub bottom_right: Point,
75    pub bottom_left: Point,
76}
77
78impl ElementQuad {
79    pub fn from_quad(quad: &Quad) -> Self {
80        assert_eq!(quad.inner().len(), 8);
81        let raw_quad = quad.inner();
82        Self {
83            top_left: Point {
84                x: raw_quad[0],
85                y: raw_quad[1],
86            },
87            top_right: Point {
88                x: raw_quad[2],
89                y: raw_quad[3],
90            },
91            bottom_right: Point {
92                x: raw_quad[4],
93                y: raw_quad[5],
94            },
95            bottom_left: Point {
96                x: raw_quad[6],
97                y: raw_quad[7],
98            },
99        }
100    }
101
102    pub fn quad_center(&self) -> Point {
103        Point {
104            x: (self.top_left.x + self.top_right.x + self.bottom_right.x + self.bottom_left.x) / 4.,
105            y: (self.top_left.y + self.top_right.y + self.bottom_right.y + self.bottom_left.y) / 4.,
106        }
107    }
108    /// Compute sum of all directed areas of adjacent triangles
109    /// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
110    pub fn quad_area(&self) -> f64 {
111        let area = self.top_left.area(&self.top_right)
112            + self.top_right.area(&self.bottom_right)
113            + self.bottom_right.area(&self.bottom_left)
114            + self.bottom_left.area(&self.top_left);
115        area.abs()
116    }
117
118    pub fn height(&self) -> f64 {
119        self.bottom_left.y - self.top_left.y
120    }
121
122    pub fn width(&self) -> f64 {
123        self.top_right.x - self.top_left.x
124    }
125
126    /// The width divided by the height
127    pub fn aspect_ratio(&self) -> f64 {
128        self.width() / self.height()
129    }
130
131    /// The most left (smallest) x-coordinate
132    pub fn most_left(&self) -> f64 {
133        self.top_right
134            .x
135            .min(self.top_left.x)
136            .min(self.bottom_right.x)
137            .min(self.bottom_left.x)
138    }
139
140    /// The most right (largest) x-coordinate
141    pub fn most_right(&self) -> f64 {
142        self.top_right
143            .x
144            .max(self.top_left.x)
145            .max(self.bottom_right.x)
146            .max(self.bottom_left.x)
147    }
148
149    /// The most top (smallest) y-coordinate
150    pub fn most_top(&self) -> f64 {
151        self.top_right
152            .y
153            .min(self.top_left.y)
154            .min(self.bottom_right.y)
155            .min(self.bottom_left.y)
156    }
157
158    /// The most bottom (largest) y-coordinate
159    pub fn most_bottom(&self) -> f64 {
160        self.top_right
161            .y
162            .max(self.top_left.y)
163            .max(self.bottom_right.y)
164            .max(self.bottom_left.y)
165    }
166
167    /// If the most bottom point of `self` is above the most top point of
168    /// `other`
169    pub fn strictly_above(&self, other: &Self) -> bool {
170        self.most_bottom() < other.most_top()
171    }
172
173    /// If the most bottom point of `self` is above or on the same line as the
174    /// most top point of `other`
175    pub fn above(&self, other: &Self) -> bool {
176        self.most_bottom() <= other.most_top()
177    }
178
179    /// If the most top point of `self` is below the most bottom point of
180    /// `other`
181    pub fn strictly_below(&self, other: &Self) -> bool {
182        self.most_top() > other.most_bottom()
183    }
184
185    /// If the most top point of `self` is below or on the same line as the
186    /// most bottom point of `other`
187    pub fn below(&self, other: &Self) -> bool {
188        self.most_top() >= other.most_bottom()
189    }
190
191    /// If the most right point of `self` is left of the most left point of
192    /// `other`
193    pub fn strictly_left_of(&self, other: &Self) -> bool {
194        self.most_right() < other.most_left()
195    }
196
197    /// If the most right point of `self` is left or on the same line as the
198    /// most left point of `other`
199    pub fn left_of(&self, other: &Self) -> bool {
200        self.most_right() <= other.most_left()
201    }
202
203    /// If the most left point of `self` is right of the most right point of
204    /// `other`
205    pub fn strictly_right_of(&self, other: &Self) -> bool {
206        self.most_left() > other.most_right()
207    }
208
209    /// If the most left point of `self` is right or on the same line as the
210    /// most right point of `other`
211    pub fn right_of(&self, other: &Self) -> bool {
212        self.most_left() >= other.most_right()
213    }
214
215    /// If `self` is within the left/right boundaries defined by `other`.
216    pub fn within_horizontal_bounds_of(&self, other: &Self) -> bool {
217        self.most_left() >= other.most_left() && self.most_right() <= other.most_right()
218    }
219
220    /// If `self` is within the top/bottom boundaries defined by `other`.
221    pub fn within_vertical_bounds_of(&self, other: &Self) -> bool {
222        self.most_top() >= other.most_top() && self.most_bottom() <= other.most_bottom()
223    }
224
225    /// If `self` is within the boundaries defined by `other`.
226    pub fn within_bounds_of(&self, other: &Self) -> bool {
227        self.within_horizontal_bounds_of(other) && self.within_vertical_bounds_of(other)
228    }
229}
230
231#[derive(Debug, Clone)]
232pub struct BoxModel {
233    pub content: ElementQuad,
234    pub padding: ElementQuad,
235    pub border: ElementQuad,
236    pub margin: ElementQuad,
237    pub width: u32,
238    pub height: u32,
239}
240
241impl BoxModel {
242    /// Create a `Viewport` equal to the content-box, using a scale of 1.0
243    pub fn content_viewport(&self) -> Viewport {
244        Viewport {
245            x: self.content.top_left.x,
246            y: self.content.top_left.y,
247            width: self.content.width(),
248            height: self.content.height(),
249            scale: 1.0,
250        }
251    }
252
253    /// Create a `Viewport` equal to the padding-box, using a scale of 1.0
254    pub fn padding_viewport(&self) -> Viewport {
255        Viewport {
256            x: self.padding.top_left.x,
257            y: self.padding.top_left.y,
258            width: self.padding.width(),
259            height: self.padding.height(),
260            scale: 1.0,
261        }
262    }
263
264    /// Create a `Viewport` equal to the border-box, using a scale of 1.0
265    pub fn border_viewport(&self) -> Viewport {
266        Viewport {
267            x: self.border.top_left.x,
268            y: self.border.top_left.y,
269            width: self.border.width(),
270            height: self.border.height(),
271            scale: 1.0,
272        }
273    }
274
275    /// Create a `Viewport` equal to the margin-box, using a scale of 1.0
276    pub fn margin_viewport(&self) -> Viewport {
277        Viewport {
278            x: self.margin.top_left.x,
279            y: self.margin.top_left.y,
280            width: self.margin.width(),
281            height: self.margin.height(),
282            scale: 1.0,
283        }
284    }
285}
286
287#[derive(Debug, Clone)]
288pub struct BoundingBox {
289    /// the x coordinate of the element in pixels.
290    pub x: f64,
291    /// the y coordinate of the element in pixels.
292    pub y: f64,
293    /// the width of the element in pixels.
294    pub width: f64,
295    /// the height of the element in pixels.
296    pub height: f64,
297}