use std::cell::Cell;
use std::default::Default;
use std::{f32, str};
use cssparser::match_ignore_ascii_case;
use dom_struct::dom_struct;
use euclid::default::Point2D;
use html5ever::{local_name, LocalName, Prefix};
use js::rust::HandleObject;
use servo_atoms::Atom;
use style::attr::AttrValue;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLAreaElementBinding::HTMLAreaElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::node::{BindContext, Node};
use crate::dom::virtualmethods::VirtualMethods;
use crate::links::{follow_hyperlink, LinkRelations};
use crate::script_runtime::CanGc;
#[derive(Debug, PartialEq)]
pub enum Area {
Circle {
left: f32,
top: f32,
radius: f32,
},
Rectangle {
top_left: (f32, f32),
bottom_right: (f32, f32),
},
Polygon {
points: Vec<f32>,
},
}
pub enum Shape {
Circle,
Rectangle,
Polygon,
}
impl Area {
pub fn parse(coord: &str, target: Shape) -> Option<Area> {
let points_count = match target {
Shape::Circle => 3,
Shape::Rectangle => 4,
Shape::Polygon => 0,
};
let size = coord.len();
let num = coord.as_bytes();
let mut index = 0;
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => {},
_ => break,
}
index += 1;
}
let mut number_list = Vec::new();
let mut array = Vec::new();
while index < size {
while index < size {
let val = num[index];
match val {
b'0'..=b'9' | b'.' | b'-' | b'E' | b'e' => break,
_ => {},
}
index += 1;
}
while index < size {
let val = num[index];
match val {
b',' | b';' | b' ' | b'\t' | b'\n' | 0x0C | b'\r' => break,
_ => array.push(val),
}
index += 1;
}
if array.is_empty() {
break;
}
match str::from_utf8(&array)
.ok()
.and_then(|s| s.parse::<f32>().ok())
{
Some(v) => number_list.push(v),
None => number_list.push(0.0),
};
array.clear();
if points_count > 0 && number_list.len() == points_count {
break;
}
}
let final_size = number_list.len();
match target {
Shape::Circle => {
if final_size == 3 {
if number_list[2] <= 0.0 {
None
} else {
Some(Area::Circle {
left: number_list[0],
top: number_list[1],
radius: number_list[2],
})
}
} else {
None
}
},
Shape::Rectangle => {
if final_size == 4 {
if number_list[0] > number_list[2] {
number_list.swap(0, 2);
}
if number_list[1] > number_list[3] {
number_list.swap(1, 3);
}
Some(Area::Rectangle {
top_left: (number_list[0], number_list[1]),
bottom_right: (number_list[2], number_list[3]),
})
} else {
None
}
},
Shape::Polygon => {
if final_size >= 6 {
if final_size % 2 != 0 {
number_list.remove(final_size - 1);
}
Some(Area::Polygon {
points: number_list,
})
} else {
None
}
},
}
}
pub fn hit_test(&self, p: &Point2D<f32>) -> bool {
match *self {
Area::Circle { left, top, radius } => {
(p.x - left) * (p.x - left) + (p.y - top) * (p.y - top) - radius * radius <= 0.0
},
Area::Rectangle {
top_left,
bottom_right,
} => {
p.x <= bottom_right.0 &&
p.x >= top_left.0 &&
p.y <= bottom_right.1 &&
p.y >= top_left.1
},
_ => false,
}
}
pub fn absolute_coords(&self, p: Point2D<f32>) -> Area {
match *self {
Area::Rectangle {
top_left,
bottom_right,
} => Area::Rectangle {
top_left: (top_left.0 + p.x, top_left.1 + p.y),
bottom_right: (bottom_right.0 + p.x, bottom_right.1 + p.y),
},
Area::Circle { left, top, radius } => Area::Circle {
left: (left + p.x),
top: (top + p.y),
radius,
},
Area::Polygon { ref points } => {
let iter = points
.iter()
.enumerate()
.map(|(index, point)| match index % 2 {
0 => point + p.x,
_ => point + p.y,
});
Area::Polygon {
points: iter.collect::<Vec<_>>(),
}
},
}
}
}
#[dom_struct]
pub struct HTMLAreaElement {
htmlelement: HTMLElement,
rel_list: MutNullableDom<DOMTokenList>,
#[no_trace]
relations: Cell<LinkRelations>,
}
impl HTMLAreaElement {
fn new_inherited(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
) -> HTMLAreaElement {
HTMLAreaElement {
htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
rel_list: Default::default(),
relations: Cell::new(LinkRelations::empty()),
}
}
#[allow(crown::unrooted_must_root)]
pub fn new(
local_name: LocalName,
prefix: Option<Prefix>,
document: &Document,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<HTMLAreaElement> {
Node::reflect_node_with_proto(
Box::new(HTMLAreaElement::new_inherited(local_name, prefix, document)),
document,
proto,
can_gc,
)
}
pub fn get_shape_from_coords(&self) -> Option<Area> {
let elem = self.upcast::<Element>();
let shape = elem.get_string_attribute(&"shape".into());
let shp: Shape = match_ignore_ascii_case! { &shape,
"circle" => Shape::Circle,
"circ" => Shape::Circle,
"rectangle" => Shape::Rectangle,
"rect" => Shape::Rectangle,
"polygon" => Shape::Rectangle,
"poly" => Shape::Polygon,
_ => return None,
};
if elem.has_attribute(&"coords".into()) {
let attribute = elem.get_string_attribute(&"coords".into());
Area::parse(&attribute, shp)
} else {
None
}
}
}
impl VirtualMethods for HTMLAreaElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
match name {
&local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()),
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(name, value),
}
}
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
self.super_type().unwrap().attribute_mutated(attr, mutation);
match *attr.local_name() {
local_name!("rel") | local_name!("rev") => {
self.relations
.set(LinkRelations::for_element(self.upcast()));
},
_ => {},
}
}
fn bind_to_tree(&self, context: &BindContext) {
if let Some(s) = self.super_type() {
s.bind_to_tree(context);
}
self.relations
.set(LinkRelations::for_element(self.upcast()));
}
}
impl HTMLAreaElementMethods<crate::DomTypeHolder> for HTMLAreaElement {
make_getter!(Target, "target");
make_setter!(SetTarget, "target");
make_getter!(Rel, "rel");
fn SetRel(&self, rel: DOMString, can_gc: CanGc) {
self.upcast::<Element>()
.set_tokenlist_attribute(&local_name!("rel"), rel, can_gc);
}
fn RelList(&self) -> DomRoot<DOMTokenList> {
self.rel_list.or_init(|| {
DOMTokenList::new(
self.upcast(),
&local_name!("rel"),
Some(vec![
Atom::from("noopener"),
Atom::from("noreferrer"),
Atom::from("opener"),
]),
)
})
}
}
impl Activatable for HTMLAreaElement {
fn as_element(&self) -> &Element {
self.upcast::<Element>()
}
fn is_instance_activatable(&self) -> bool {
self.as_element().has_attribute(&local_name!("href"))
}
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, _can_gc: CanGc) {
follow_hyperlink(self.as_element(), self.relations.get(), None);
}
}