script/dom/html/
htmlfontelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use cssparser::match_ignore_ascii_case;
6use dom_struct::dom_struct;
7use html5ever::{LocalName, Prefix, local_name, ns};
8use js::context::JSContext;
9use js::rust::HandleObject;
10use style::attr::AttrValue;
11use style::color::AbsoluteColor;
12use style::str::{HTML_SPACE_CHARACTERS, read_numbers};
13use style::values::computed::font::{
14    FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
15};
16use stylo_atoms::Atom;
17
18use crate::dom::attr::Attr;
19use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::root::{DomRoot, LayoutDom};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::document::Document;
24use crate::dom::element::Element;
25use crate::dom::html::htmlelement::HTMLElement;
26use crate::dom::node::Node;
27use crate::dom::virtualmethods::VirtualMethods;
28use crate::script_runtime::CanGc;
29
30#[dom_struct]
31pub(crate) struct HTMLFontElement {
32    htmlelement: HTMLElement,
33}
34
35impl HTMLFontElement {
36    fn new_inherited(
37        local_name: LocalName,
38        prefix: Option<Prefix>,
39        document: &Document,
40    ) -> HTMLFontElement {
41        HTMLFontElement {
42            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
43        }
44    }
45
46    pub(crate) fn new(
47        cx: &mut JSContext,
48        local_name: LocalName,
49        prefix: Option<Prefix>,
50        document: &Document,
51        proto: Option<HandleObject>,
52    ) -> DomRoot<HTMLFontElement> {
53        Node::reflect_node_with_proto(
54            cx,
55            Box::new(HTMLFontElement::new_inherited(local_name, prefix, document)),
56            document,
57            proto,
58        )
59    }
60
61    pub(crate) fn parse_face_attribute(face_value: Atom) -> Vec<SingleFontFamily> {
62        face_value
63            .split(',')
64            .map(|string| Self::parse_single_face_value_from_string(string.trim()))
65            .collect()
66    }
67
68    fn parse_single_face_value_from_string(string: &str) -> SingleFontFamily {
69        match_ignore_ascii_case! { string,
70            "serif" => return SingleFontFamily::Generic(GenericFontFamily::Serif),
71            "sans-serif" => return SingleFontFamily::Generic(GenericFontFamily::SansSerif),
72            "cursive" => return SingleFontFamily::Generic(GenericFontFamily::Cursive),
73            "fantasy" => return SingleFontFamily::Generic(GenericFontFamily::Fantasy),
74            "monospace" => return SingleFontFamily::Generic(GenericFontFamily::Monospace),
75            "system-ui" => return SingleFontFamily::Generic(GenericFontFamily::SystemUi),
76            _ => {}
77        }
78
79        let name = string.to_owned().replace(['\'', '"'], "");
80        let syntax = if name == string {
81            FontFamilyNameSyntax::Identifiers
82        } else {
83            FontFamilyNameSyntax::Quoted
84        };
85
86        SingleFontFamily::FamilyName(FamilyName {
87            name: name.into(),
88            syntax,
89        })
90    }
91}
92
93impl HTMLFontElementMethods<crate::DomTypeHolder> for HTMLFontElement {
94    // https://html.spec.whatwg.org/multipage/#dom-font-color
95    make_getter!(Color, "color");
96
97    // https://html.spec.whatwg.org/multipage/#dom-font-color
98    make_legacy_color_setter!(SetColor, "color");
99
100    // https://html.spec.whatwg.org/multipage/#dom-font-face
101    make_getter!(Face, "face");
102
103    // https://html.spec.whatwg.org/multipage/#dom-font-face
104    make_atomic_setter!(cx, SetFace, "face");
105
106    // https://html.spec.whatwg.org/multipage/#dom-font-size
107    make_getter!(Size, "size");
108
109    /// <https://html.spec.whatwg.org/multipage/#dom-font-size>
110    fn SetSize(&self, cx: &mut JSContext, value: DOMString) {
111        let element = self.upcast::<Element>();
112        element.set_attribute(&local_name!("size"), parse_size(&value), CanGc::from_cx(cx));
113    }
114}
115
116impl VirtualMethods for HTMLFontElement {
117    fn super_type(&self) -> Option<&dyn VirtualMethods> {
118        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
119    }
120
121    fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
122        if attr.local_name() == &local_name!("color") ||
123            attr.local_name() == &local_name!("size") ||
124            attr.local_name() == &local_name!("face")
125        {
126            return true;
127        }
128
129        self.super_type()
130            .unwrap()
131            .attribute_affects_presentational_hints(attr)
132    }
133
134    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
135        match *name {
136            local_name!("face") => AttrValue::from_atomic(value.into()),
137            local_name!("color") => AttrValue::from_legacy_color(value.into()),
138            local_name!("size") => parse_size(&value),
139            _ => self
140                .super_type()
141                .unwrap()
142                .parse_plain_attribute(name, value),
143        }
144    }
145}
146
147impl LayoutDom<'_, HTMLFontElement> {
148    pub(crate) fn get_color(self) -> Option<AbsoluteColor> {
149        self.upcast::<Element>()
150            .get_attr_for_layout(&ns!(), &local_name!("color"))
151            .and_then(AttrValue::as_color)
152            .cloned()
153    }
154
155    pub(crate) fn get_face(self) -> Option<Atom> {
156        let face = self
157            .upcast::<Element>()
158            .get_attr_for_layout(&ns!(), &local_name!("face"));
159        match face {
160            Some(AttrValue::Atom(s)) => Some(s.clone()),
161            _ => None,
162        }
163    }
164
165    pub(crate) fn get_size(self) -> Option<u32> {
166        let size = self
167            .upcast::<Element>()
168            .get_attr_for_layout(&ns!(), &local_name!("size"));
169        match size {
170            Some(&AttrValue::UInt(_, s)) => Some(s),
171            _ => None,
172        }
173    }
174}
175
176/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size>
177fn parse_size(input: &DOMString) -> AttrValue {
178    let original_input = input;
179    // Steps 1 & 2 are not relevant
180
181    // Step 3
182    let input = input.str();
183    let input = input.trim_matches(HTML_SPACE_CHARACTERS);
184
185    enum ParseMode {
186        RelativePlus,
187        RelativeMinus,
188        Absolute,
189    }
190    let mut input_chars = input.chars().peekable();
191    let parse_mode = match input_chars.peek() {
192        // Step 4
193        None => return AttrValue::String(original_input.str().into()),
194
195        // Step 5
196        Some(&'+') => {
197            let _ = input_chars.next(); // consume the '+'
198            ParseMode::RelativePlus
199        },
200        Some(&'-') => {
201            let _ = input_chars.next(); // consume the '-'
202            ParseMode::RelativeMinus
203        },
204        Some(_) => ParseMode::Absolute,
205    };
206
207    // Steps 6, 7, 8
208    let mut value = match read_numbers(input_chars) {
209        (Some(v), _) if v >= 0 => v,
210        _ => return AttrValue::String(original_input.str().into()),
211    };
212
213    // Step 9
214    match parse_mode {
215        ParseMode::RelativePlus => value += 3,
216        ParseMode::RelativeMinus => value = 3 - value,
217        ParseMode::Absolute => (),
218    }
219
220    // Step 10: If value is greater than 7, let it be 7.
221    // Step 11: If value is less than 1, let it be 1.
222    value = value.clamp(1, 7);
223
224    // Step 12
225    AttrValue::UInt(original_input.str().into(), value as u32)
226}