servo-script 0.1.0

A component of the servo web-engine.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use dom_struct::dom_struct;
use js::rust::HandleObject;
use rustc_hash::FxHashMap;
use servo_base::id::{DomQuadId, DomQuadIndex};
use servo_constellation_traits::{DomPoint, DomQuad};

use crate::dom::bindings::codegen::Bindings::DOMPointBinding::{DOMPointInit, DOMPointMethods};
use crate::dom::bindings::codegen::Bindings::DOMQuadBinding::{DOMQuadInit, DOMQuadMethods};
use crate::dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectInit;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::serializable::Serializable;
use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::dompoint::DOMPoint;
use crate::dom::domrect::DOMRect;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;

/// <https://drafts.fxtf.org/geometry/#DOMQuad>
#[dom_struct]
pub(crate) struct DOMQuad {
    reflector_: Reflector,
    p1: Dom<DOMPoint>,
    p2: Dom<DOMPoint>,
    p3: Dom<DOMPoint>,
    p4: Dom<DOMPoint>,
}

impl DOMQuad {
    fn new_inherited(p1: &DOMPoint, p2: &DOMPoint, p3: &DOMPoint, p4: &DOMPoint) -> DOMQuad {
        DOMQuad {
            reflector_: Reflector::new(),
            p1: Dom::from_ref(p1),
            p2: Dom::from_ref(p2),
            p3: Dom::from_ref(p3),
            p4: Dom::from_ref(p4),
        }
    }

    pub(crate) fn new(
        global: &GlobalScope,
        p1: &DOMPoint,
        p2: &DOMPoint,
        p3: &DOMPoint,
        p4: &DOMPoint,
        can_gc: CanGc,
    ) -> DomRoot<DOMQuad> {
        Self::new_with_proto(global, None, p1, p2, p3, p4, can_gc)
    }

    fn new_with_proto(
        global: &GlobalScope,
        proto: Option<HandleObject>,
        p1: &DOMPoint,
        p2: &DOMPoint,
        p3: &DOMPoint,
        p4: &DOMPoint,
        can_gc: CanGc,
    ) -> DomRoot<DOMQuad> {
        reflect_dom_object_with_proto(
            Box::new(DOMQuad::new_inherited(p1, p2, p3, p4)),
            global,
            proto,
            can_gc,
        )
    }
}

impl DOMQuadMethods<crate::DomTypeHolder> for DOMQuad {
    /// <https://drafts.fxtf.org/geometry/#dom-domquad-domquad>
    fn Constructor(
        global: &GlobalScope,
        proto: Option<HandleObject>,
        can_gc: CanGc,
        p1: &DOMPointInit,
        p2: &DOMPointInit,
        p3: &DOMPointInit,
        p4: &DOMPointInit,
    ) -> Fallible<DomRoot<DOMQuad>> {
        Ok(DOMQuad::new_with_proto(
            global,
            proto,
            &DOMPoint::new_from_init(global, p1, can_gc),
            &DOMPoint::new_from_init(global, p2, can_gc),
            &DOMPoint::new_from_init(global, p3, can_gc),
            &DOMPoint::new_from_init(global, p4, can_gc),
            can_gc,
        ))
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-fromrect>
    fn FromRect(global: &GlobalScope, other: &DOMRectInit, can_gc: CanGc) -> DomRoot<DOMQuad> {
        DOMQuad::new(
            global,
            &DOMPoint::new(global, other.x, other.y, 0f64, 1f64, can_gc),
            &DOMPoint::new(global, other.x + other.width, other.y, 0f64, 1f64, can_gc),
            &DOMPoint::new(
                global,
                other.x + other.width,
                other.y + other.height,
                0f64,
                1f64,
                can_gc,
            ),
            &DOMPoint::new(global, other.x, other.y + other.height, 0f64, 1f64, can_gc),
            can_gc,
        )
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-fromquad>
    fn FromQuad(global: &GlobalScope, other: &DOMQuadInit, can_gc: CanGc) -> DomRoot<DOMQuad> {
        DOMQuad::new(
            global,
            &DOMPoint::new_from_init(global, &other.p1, can_gc),
            &DOMPoint::new_from_init(global, &other.p2, can_gc),
            &DOMPoint::new_from_init(global, &other.p3, can_gc),
            &DOMPoint::new_from_init(global, &other.p4, can_gc),
            can_gc,
        )
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-p1>
    fn P1(&self) -> DomRoot<DOMPoint> {
        DomRoot::from_ref(&self.p1)
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-p2>
    fn P2(&self) -> DomRoot<DOMPoint> {
        DomRoot::from_ref(&self.p2)
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-p3>
    fn P3(&self) -> DomRoot<DOMPoint> {
        DomRoot::from_ref(&self.p3)
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-p4>
    fn P4(&self) -> DomRoot<DOMPoint> {
        DomRoot::from_ref(&self.p4)
    }

    /// <https://drafts.fxtf.org/geometry/#dom-domquad-getbounds>
    fn GetBounds(&self, can_gc: CanGc) -> DomRoot<DOMRect> {
        // https://drafts.fxtf.org/geometry/#nan-safe-minimum
        let nan_safe_minimum = |a: f64, b: f64| {
            if a.is_nan() || b.is_nan() {
                f64::NAN
            } else {
                a.min(b)
            }
        };

        // https://drafts.fxtf.org/geometry/#nan-safe-maximum
        let nan_safe_maximum = |a: f64, b: f64| {
            if a.is_nan() || b.is_nan() {
                f64::NAN
            } else {
                a.max(b)
            }
        };

        // Step 1. Let bounds be a DOMRect object.
        // NOTE: We construct the object at the end

        // Step 2. Let left be the NaN-safe minimum of point 1’s x coordinate,
        // point 2’s x coordinate, point 3’s x coordinate and point 4’s x coordinate.
        let left = nan_safe_minimum(
            nan_safe_minimum(self.p1.X(), self.p2.X()),
            nan_safe_minimum(self.p3.X(), self.p4.X()),
        );

        // Step 3. Let top be the NaN-safe minimum of point 1’s y coordinate,
        // point 2’s y coordinate, point 3’s y coordinate and point 4’s y coordinate.
        let top = nan_safe_minimum(
            nan_safe_minimum(self.p1.Y(), self.p2.Y()),
            nan_safe_minimum(self.p3.Y(), self.p4.Y()),
        );

        // Step 4. Let right be the NaN-safe maximum of point 1’s x coordinate,
        // point 2’s x coordinate, point 3’s x coordinate and point 4’s x coordinate.
        let right = nan_safe_maximum(
            nan_safe_maximum(self.p1.X(), self.p2.X()),
            nan_safe_maximum(self.p3.X(), self.p4.X()),
        );

        // Step 5. Let bottom be the NaN-safe maximum of point 1’s y coordinate,
        // point 2’s y coordinate, point 3’s y coordinate and point 4’s y coordinate.
        let bottom = nan_safe_maximum(
            nan_safe_maximum(self.p1.Y(), self.p2.Y()),
            nan_safe_maximum(self.p3.Y(), self.p4.Y()),
        );

        // Step 6. Set x coordinate of bounds to left, y coordinate of bounds to top,
        // width dimension of bounds to right - left and height dimension of bounds to bottom - top.
        // NOTE: Combined with Step 1.
        DOMRect::new(
            &self.global(),
            left,
            top,
            right - left,
            bottom - top,
            can_gc,
        )
    }
}

impl Serializable for DOMQuad {
    type Index = DomQuadIndex;
    type Data = DomQuad;

    fn serialize(&self) -> Result<(DomQuadId, Self::Data), ()> {
        let make_point = |src: DomRoot<DOMPoint>| -> DomPoint {
            DomPoint {
                x: src.X(),
                y: src.Y(),
                z: src.Z(),
                w: src.W(),
            }
        };
        let serialized = DomQuad {
            p1: make_point(self.P1()),
            p2: make_point(self.P2()),
            p3: make_point(self.P3()),
            p4: make_point(self.P4()),
        };
        Ok((DomQuadId::new(), serialized))
    }

    fn deserialize(
        owner: &GlobalScope,
        serialized: Self::Data,
        can_gc: CanGc,
    ) -> Result<DomRoot<Self>, ()>
    where
        Self: Sized,
    {
        let make_point = |src: DomPoint| -> DomRoot<DOMPoint> {
            DOMPoint::new(owner, src.x, src.y, src.z, src.w, can_gc)
        };
        Ok(Self::new(
            owner,
            &make_point(serialized.p1),
            &make_point(serialized.p2),
            &make_point(serialized.p3),
            &make_point(serialized.p4),
            can_gc,
        ))
    }

    fn serialized_storage<'a>(
        data: StructuredData<'a, '_>,
    ) -> &'a mut Option<FxHashMap<DomQuadId, Self::Data>> {
        match data {
            StructuredData::Reader(reader) => &mut reader.quads,
            StructuredData::Writer(writer) => &mut writer.quads,
        }
    }
}