euclid/
side_offsets.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! A group of side offsets, which correspond to top/left/bottom/right for borders, padding,
11//! and margins in CSS.
12
13use crate::length::Length;
14use crate::num::Zero;
15use crate::scale::Scale;
16use crate::Vector2D;
17
18use core::cmp::{Eq, PartialEq};
19use core::fmt;
20use core::hash::Hash;
21use core::marker::PhantomData;
22use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
23
24#[cfg(feature = "bytemuck")]
25use bytemuck::{Pod, Zeroable};
26#[cfg(feature = "malloc_size_of")]
27use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31/// A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding,
32/// and margins in CSS, optionally tagged with a unit.
33#[repr(C)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(
36    feature = "serde",
37    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
38)]
39pub struct SideOffsets2D<T, U> {
40    pub top: T,
41    pub right: T,
42    pub bottom: T,
43    pub left: T,
44    #[doc(hidden)]
45    pub _unit: PhantomData<U>,
46}
47
48#[cfg(feature = "arbitrary")]
49impl<'a, T, U> arbitrary::Arbitrary<'a> for SideOffsets2D<T, U>
50where
51    T: arbitrary::Arbitrary<'a>,
52{
53    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
54        let (top, right, bottom, left) = arbitrary::Arbitrary::arbitrary(u)?;
55        Ok(SideOffsets2D {
56            top,
57            right,
58            bottom,
59            left,
60            _unit: PhantomData,
61        })
62    }
63}
64
65#[cfg(feature = "bytemuck")]
66unsafe impl<T: Zeroable, U> Zeroable for SideOffsets2D<T, U> {}
67
68#[cfg(feature = "bytemuck")]
69unsafe impl<T: Pod, U: 'static> Pod for SideOffsets2D<T, U> {}
70
71#[cfg(feature = "malloc_size_of")]
72impl<T: MallocSizeOf, U> MallocSizeOf for SideOffsets2D<T, U> {
73    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
74        self.top.size_of(ops)
75            + self.right.size_of(ops)
76            + self.bottom.size_of(ops)
77            + self.left.size_of(ops)
78    }
79}
80
81impl<T: Copy, U> Copy for SideOffsets2D<T, U> {}
82
83impl<T: Clone, U> Clone for SideOffsets2D<T, U> {
84    fn clone(&self) -> Self {
85        SideOffsets2D {
86            top: self.top.clone(),
87            right: self.right.clone(),
88            bottom: self.bottom.clone(),
89            left: self.left.clone(),
90            _unit: PhantomData,
91        }
92    }
93}
94
95impl<T, U> Eq for SideOffsets2D<T, U> where T: Eq {}
96
97impl<T, U> PartialEq for SideOffsets2D<T, U>
98where
99    T: PartialEq,
100{
101    fn eq(&self, other: &Self) -> bool {
102        self.top == other.top
103            && self.right == other.right
104            && self.bottom == other.bottom
105            && self.left == other.left
106    }
107}
108
109impl<T, U> Hash for SideOffsets2D<T, U>
110where
111    T: Hash,
112{
113    fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
114        self.top.hash(h);
115        self.right.hash(h);
116        self.bottom.hash(h);
117        self.left.hash(h);
118    }
119}
120
121impl<T: fmt::Debug, U> fmt::Debug for SideOffsets2D<T, U> {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        write!(
124            f,
125            "({:?},{:?},{:?},{:?})",
126            self.top, self.right, self.bottom, self.left
127        )
128    }
129}
130
131impl<T: Default, U> Default for SideOffsets2D<T, U> {
132    fn default() -> Self {
133        SideOffsets2D {
134            top: Default::default(),
135            right: Default::default(),
136            bottom: Default::default(),
137            left: Default::default(),
138            _unit: PhantomData,
139        }
140    }
141}
142
143impl<T, U> SideOffsets2D<T, U> {
144    /// Constructor taking a scalar for each side.
145    ///
146    /// Sides are specified in top-right-bottom-left order following
147    /// CSS's convention.
148    pub const fn new(top: T, right: T, bottom: T, left: T) -> Self {
149        SideOffsets2D {
150            top,
151            right,
152            bottom,
153            left,
154            _unit: PhantomData,
155        }
156    }
157
158    /// Constructor taking a typed Length for each side.
159    ///
160    /// Sides are specified in top-right-bottom-left order following
161    /// CSS's convention.
162    pub fn from_lengths(
163        top: Length<T, U>,
164        right: Length<T, U>,
165        bottom: Length<T, U>,
166        left: Length<T, U>,
167    ) -> Self {
168        SideOffsets2D::new(top.0, right.0, bottom.0, left.0)
169    }
170
171    /// Construct side offsets from min and a max vector offsets.
172    ///
173    /// The outer rect of the resulting side offsets is equivalent to translating
174    /// a rectangle's upper-left corner with the min vector and translating the
175    /// bottom-right corner with the max vector.
176    pub fn from_vectors_outer(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
177    where
178        T: Neg<Output = T>,
179    {
180        SideOffsets2D {
181            left: -min.x,
182            top: -min.y,
183            right: max.x,
184            bottom: max.y,
185            _unit: PhantomData,
186        }
187    }
188
189    /// Construct side offsets from min and a max vector offsets.
190    ///
191    /// The inner rect of the resulting side offsets is equivalent to translating
192    /// a rectangle's upper-left corner with the min vector and translating the
193    /// bottom-right corner with the max vector.
194    pub fn from_vectors_inner(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
195    where
196        T: Neg<Output = T>,
197    {
198        SideOffsets2D {
199            left: min.x,
200            top: min.y,
201            right: -max.x,
202            bottom: -max.y,
203            _unit: PhantomData,
204        }
205    }
206
207    /// Constructor, setting all sides to zero.
208    pub fn zero() -> Self
209    where
210        T: Zero,
211    {
212        SideOffsets2D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
213    }
214
215    /// Returns `true` if all side offsets are zero.
216    pub fn is_zero(&self) -> bool
217    where
218        T: Zero + PartialEq,
219    {
220        let zero = T::zero();
221        self.top == zero && self.right == zero && self.bottom == zero && self.left == zero
222    }
223
224    /// Constructor setting the same value to all sides, taking a scalar value directly.
225    pub fn new_all_same(all: T) -> Self
226    where
227        T: Copy,
228    {
229        SideOffsets2D::new(all, all, all, all)
230    }
231
232    /// Constructor setting the same value to all sides, taking a typed Length.
233    pub fn from_length_all_same(all: Length<T, U>) -> Self
234    where
235        T: Copy,
236    {
237        SideOffsets2D::new_all_same(all.0)
238    }
239
240    pub fn horizontal(&self) -> T
241    where
242        T: Copy + Add<T, Output = T>,
243    {
244        self.left + self.right
245    }
246
247    pub fn vertical(&self) -> T
248    where
249        T: Copy + Add<T, Output = T>,
250    {
251        self.top + self.bottom
252    }
253}
254
255impl<T, U> Add for SideOffsets2D<T, U>
256where
257    T: Add<T, Output = T>,
258{
259    type Output = Self;
260    fn add(self, other: Self) -> Self {
261        SideOffsets2D::new(
262            self.top + other.top,
263            self.right + other.right,
264            self.bottom + other.bottom,
265            self.left + other.left,
266        )
267    }
268}
269
270impl<T, U> AddAssign<Self> for SideOffsets2D<T, U>
271where
272    T: AddAssign<T>,
273{
274    fn add_assign(&mut self, other: Self) {
275        self.top += other.top;
276        self.right += other.right;
277        self.bottom += other.bottom;
278        self.left += other.left;
279    }
280}
281
282impl<T, U> Sub for SideOffsets2D<T, U>
283where
284    T: Sub<T, Output = T>,
285{
286    type Output = Self;
287    fn sub(self, other: Self) -> Self {
288        SideOffsets2D::new(
289            self.top - other.top,
290            self.right - other.right,
291            self.bottom - other.bottom,
292            self.left - other.left,
293        )
294    }
295}
296
297impl<T, U> SubAssign<Self> for SideOffsets2D<T, U>
298where
299    T: SubAssign<T>,
300{
301    fn sub_assign(&mut self, other: Self) {
302        self.top -= other.top;
303        self.right -= other.right;
304        self.bottom -= other.bottom;
305        self.left -= other.left;
306    }
307}
308
309impl<T, U> Neg for SideOffsets2D<T, U>
310where
311    T: Neg<Output = T>,
312{
313    type Output = Self;
314    fn neg(self) -> Self {
315        SideOffsets2D {
316            top: -self.top,
317            right: -self.right,
318            bottom: -self.bottom,
319            left: -self.left,
320            _unit: PhantomData,
321        }
322    }
323}
324
325impl<T: Copy + Mul, U> Mul<T> for SideOffsets2D<T, U> {
326    type Output = SideOffsets2D<T::Output, U>;
327
328    #[inline]
329    fn mul(self, scale: T) -> Self::Output {
330        SideOffsets2D::new(
331            self.top * scale,
332            self.right * scale,
333            self.bottom * scale,
334            self.left * scale,
335        )
336    }
337}
338
339impl<T: Copy + MulAssign, U> MulAssign<T> for SideOffsets2D<T, U> {
340    #[inline]
341    fn mul_assign(&mut self, other: T) {
342        self.top *= other;
343        self.right *= other;
344        self.bottom *= other;
345        self.left *= other;
346    }
347}
348
349impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for SideOffsets2D<T, U1> {
350    type Output = SideOffsets2D<T::Output, U2>;
351
352    #[inline]
353    fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
354        SideOffsets2D::new(
355            self.top * scale.0,
356            self.right * scale.0,
357            self.bottom * scale.0,
358            self.left * scale.0,
359        )
360    }
361}
362
363impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
364    #[inline]
365    fn mul_assign(&mut self, other: Scale<T, U, U>) {
366        *self *= other.0;
367    }
368}
369
370impl<T: Copy + Div, U> Div<T> for SideOffsets2D<T, U> {
371    type Output = SideOffsets2D<T::Output, U>;
372
373    #[inline]
374    fn div(self, scale: T) -> Self::Output {
375        SideOffsets2D::new(
376            self.top / scale,
377            self.right / scale,
378            self.bottom / scale,
379            self.left / scale,
380        )
381    }
382}
383
384impl<T: Copy + DivAssign, U> DivAssign<T> for SideOffsets2D<T, U> {
385    #[inline]
386    fn div_assign(&mut self, other: T) {
387        self.top /= other;
388        self.right /= other;
389        self.bottom /= other;
390        self.left /= other;
391    }
392}
393
394impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for SideOffsets2D<T, U2> {
395    type Output = SideOffsets2D<T::Output, U1>;
396
397    #[inline]
398    fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
399        SideOffsets2D::new(
400            self.top / scale.0,
401            self.right / scale.0,
402            self.bottom / scale.0,
403            self.left / scale.0,
404        )
405    }
406}
407
408impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
409    fn div_assign(&mut self, other: Scale<T, U, U>) {
410        *self /= other.0;
411    }
412}
413
414#[cfg(test)]
415#[cfg(any(feature = "std", feature = "libm"))]
416mod tests {
417    use crate::SideOffsets2D;
418
419    #[test]
420    fn from_vectors() {
421        use crate::{point2, vec2};
422        type Box2D = crate::default::Box2D<i32>;
423
424        let b = Box2D {
425            min: point2(10, 10),
426            max: point2(20, 20),
427        };
428
429        let outer = b.outer_box(SideOffsets2D::from_vectors_outer(vec2(-1, -2), vec2(3, 4)));
430        let inner = b.inner_box(SideOffsets2D::from_vectors_inner(vec2(1, 2), vec2(-3, -4)));
431
432        assert_eq!(
433            outer,
434            Box2D {
435                min: point2(9, 8),
436                max: point2(23, 24)
437            }
438        );
439        assert_eq!(
440            inner,
441            Box2D {
442                min: point2(11, 12),
443                max: point2(17, 16)
444            }
445        );
446    }
447
448    #[test]
449    fn test_is_zero() {
450        let s1: SideOffsets2D<f32, ()> = SideOffsets2D::new_all_same(0.0);
451        assert!(s1.is_zero());
452
453        let s2: SideOffsets2D<f32, ()> = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
454        assert!(!s2.is_zero());
455    }
456
457    #[cfg(test)]
458    mod ops {
459        use crate::Scale;
460
461        pub enum Mm {}
462        pub enum Cm {}
463
464        type SideOffsets2D<T> = crate::default::SideOffsets2D<T>;
465        type SideOffsets2DMm<T> = crate::SideOffsets2D<T, Mm>;
466        type SideOffsets2DCm<T> = crate::SideOffsets2D<T, Cm>;
467
468        #[test]
469        fn test_mul_scalar() {
470            let s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
471
472            let result = s * 3.0;
473
474            assert_eq!(result, SideOffsets2D::new(3.0, 6.0, 9.0, 12.0));
475        }
476
477        #[test]
478        fn test_mul_assign_scalar() {
479            let mut s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
480
481            s *= 2.0;
482
483            assert_eq!(s, SideOffsets2D::new(2.0, 4.0, 6.0, 8.0));
484        }
485
486        #[test]
487        fn test_mul_scale() {
488            let s = SideOffsets2DMm::new(0.0, 1.0, 3.0, 2.0);
489            let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
490
491            let result = s * cm_per_mm;
492
493            assert_eq!(result, SideOffsets2DCm::new(0.0, 0.1, 0.3, 0.2));
494        }
495
496        #[test]
497        fn test_mul_assign_scale() {
498            let mut s = SideOffsets2DMm::new(2.0, 4.0, 6.0, 8.0);
499            let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
500
501            s *= scale;
502
503            assert_eq!(s, SideOffsets2DMm::new(0.2, 0.4, 0.6, 0.8));
504        }
505
506        #[test]
507        fn test_div_scalar() {
508            let s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
509
510            let result = s / 10.0;
511
512            assert_eq!(result, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
513        }
514
515        #[test]
516        fn test_div_assign_scalar() {
517            let mut s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
518
519            s /= 10.0;
520
521            assert_eq!(s, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
522        }
523
524        #[test]
525        fn test_div_scale() {
526            let s = SideOffsets2DCm::new(0.1, 0.2, 0.3, 0.4);
527            let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
528
529            let result = s / cm_per_mm;
530
531            assert_eq!(result, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
532        }
533
534        #[test]
535        fn test_div_assign_scale() {
536            let mut s = SideOffsets2DMm::new(0.1, 0.2, 0.3, 0.4);
537            let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
538
539            s /= scale;
540
541            assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
542        }
543    }
544}