script/dom/execcommand/commands/
fontsize.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 app_units::Au;
6use servo_config::pref;
7use style::attr::parse_integer;
8use style::values::computed::CSSPixelLength;
9use style::values::specified::FontSize;
10
11use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
12use crate::dom::bindings::str::DOMString;
13use crate::dom::document::Document;
14use crate::dom::execcommand::basecommand::CommandName;
15use crate::dom::execcommand::contenteditable::{
16    NodeExecCommandSupport, SelectionExecCommandSupport,
17};
18use crate::dom::selection::Selection;
19use crate::script_runtime::CanGc;
20
21/// <https://w3c.github.io/editing/docs/execCommand/#legacy-font-size-for>
22pub(crate) fn legacy_font_size_for(pixel_size: f32, document: &Document) -> DOMString {
23    let quirks_mode = document.quirks_mode();
24    let base_size = CSSPixelLength::from(Au::from_f32_px(pref!(fonts_default_size) as f32));
25    // Step 1. Let returned size be 1.
26    let mut returned_size = 1;
27    // Step 2. While returned size is less than 7:
28    while returned_size < 7 {
29        // Step 2.1. Let lower bound be the resolved value of "font-size" in pixels
30        // of a font element whose size attribute is set to returned size.
31        let FontSize::Keyword(lower_keyword) = FontSize::from_html_size(returned_size) else {
32            unreachable!("Always computed as keyword");
33        };
34        let lower_bound = lower_keyword
35            .kw
36            .to_length_without_context(quirks_mode, base_size);
37        // Step 2.2. Let upper bound be the resolved value of "font-size" in pixels
38        // of a font element whose size attribute is set to one plus returned size.
39        let FontSize::Keyword(upper_keyword) = FontSize::from_html_size(returned_size + 1) else {
40            unreachable!("Always computed as keyword");
41        };
42        let upper_bound = upper_keyword
43            .kw
44            .to_length_without_context(quirks_mode, base_size);
45        // Step 2.3. Let average be the average of upper bound and lower bound.
46        let average = (lower_bound.0.px() + upper_bound.0.px()) / 2.0;
47        // Step 2.4. If pixel size is less than average,
48        // return the one-code unit string consisting of the digit returned size.
49        //
50        // We return once at the end of this method
51        if pixel_size < average {
52            break;
53        }
54        // Step 2.5. Add one to returned size.
55        returned_size += 1;
56    }
57    // Step 3. Return "7".
58    returned_size.to_string().into()
59}
60
61enum ParsingMode {
62    RelativePlus,
63    RelativeMinus,
64    Absolute,
65}
66
67/// <https://w3c.github.io/editing/docs/execCommand/#the-fontsize-command>
68pub(crate) fn execute_fontsize_command(
69    cx: &mut js::context::JSContext,
70    document: &Document,
71    selection: &Selection,
72    value: DOMString,
73) -> bool {
74    // Step 1. Strip leading and trailing whitespace from value.
75    let value = {
76        let mut value = value;
77        value.strip_leading_and_trailing_ascii_whitespace();
78        value
79    };
80    // Step 2. If value is not a valid floating point number,
81    // and would not be a valid floating point number if a single leading "+" character were stripped, return false.
82    //
83    // The second part is checked in conjunction with step 3 for optimization
84    if !value.is_valid_floating_point_number_string() {
85        return false;
86    }
87    // Step 3. If the first character of value is "+",
88    // delete the character and let mode be "relative-plus".
89    let (value, mode) = if value.starts_with('+') {
90        let stripped_plus = &value.str()[1..];
91        // FIXME: This is not optimal, but not sure how to both delete the first character and check here
92        if !DOMString::from(stripped_plus).is_valid_floating_point_number_string() {
93            return false;
94        }
95        (stripped_plus.to_owned(), ParsingMode::RelativePlus)
96    } else if value.starts_with('-') {
97        // Step 4. Otherwise, if the first character of value is "-",
98        // delete the character and let mode be "relative-minus".
99        (value.str()[1..].to_owned(), ParsingMode::RelativeMinus)
100    } else {
101        // Step 5. Otherwise, let mode be "absolute".
102        (value.into(), ParsingMode::Absolute)
103    };
104    // Step 6. Apply the rules for parsing non-negative integers to value, and let number be the result.
105    let number = parse_integer(value.chars()).expect("Already validated floating number before");
106    let number = match mode {
107        // Step 7. If mode is "relative-plus", add three to number.
108        ParsingMode::RelativePlus => number + 3,
109        // Step 8. If mode is "relative-minus", negate number, then add three to it.
110        ParsingMode::RelativeMinus => (-number) + 3,
111        ParsingMode::Absolute => number,
112    };
113    // Step 9. If number is less than one, let number equal 1.
114    // Step 10. If number is greater than seven, let number equal 7.
115    let number = number.clamp(1, 7);
116    // Step 11. Set value to the string here corresponding to number:
117    let value = match number {
118        1 => "x-small",
119        2 => "small",
120        3 => "medium",
121        4 => "large",
122        5 => "x-large",
123        6 => "xx-large",
124        7 => "xxx-large",
125        _ => unreachable!("Must be bounded by 1 and 7"),
126    };
127    // Step 12. Set the selection's value to value.
128    selection.set_the_selection_value(cx, Some(value.into()), CommandName::FontSize, document);
129    // Step 13. Return true.
130    true
131}
132
133/// <https://w3c.github.io/editing/docs/execCommand/#the-fontsize-command>
134pub(crate) fn value_for_fontsize_command(
135    cx: &mut js::context::JSContext,
136    document: &Document,
137) -> Option<DOMString> {
138    // Step 1. If the active range is null, return the empty string.
139    let selection = document.GetSelection(CanGc::from_cx(cx))?;
140    let active_range = selection.active_range()?;
141    // Step 2. Let pixel size be the effective command value of the first formattable
142    // node that is effectively contained in the active range, or if there is no such node,
143    // the effective command value of the active range's start node,
144    // in either case interpreted as a number of pixels.
145    let command_value = active_range
146        .first_formattable_contained_node()
147        .unwrap_or_else(|| active_range.start_container())
148        .effective_command_value(&CommandName::FontSize)?;
149    // Step 3. Return the legacy font size for pixel size.
150    //
151    // Only in the case we have resolved to actual pixels, we need to
152    // do its conversion. In other cases, we already have the relevant
153    // font size or corresponding css value. This avoids expensive
154    // conversions of pixels to other values.
155    if command_value.ends_with_str("px") {
156        command_value.str()[0..command_value.len() - 2]
157            .parse::<f32>()
158            .ok()
159            .map(|value| legacy_font_size_for(value, document))
160    } else {
161        Some(normalize_font_string(&command_value.str()).into())
162    }
163}
164
165fn normalize_font_string(str_: &str) -> &str {
166    match str_ {
167        "x-small" => "1",
168        "small" => "2",
169        "medium" => "3",
170        "large" => "4",
171        "x-large" => "5",
172        "xx-large" => "6",
173        "xxx-large" => "7",
174        _ => str_,
175    }
176}
177
178/// Handles fontsize command part of
179/// <https://w3c.github.io/editing/docs/execCommand/#loosely-equivalent-values>
180pub(crate) fn font_size_loosely_equivalent(first: &DOMString, second: &DOMString) -> bool {
181    // > one of the quantities is one of "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large";
182    // > and the other quantity is the resolved value of "font-size" on a font element whose size attribute
183    // > has the corresponding value set ("1" through "7" respectively).
184    normalize_font_string(&first.str()) == second || first == normalize_font_string(&second.str())
185}