Skip to main content

blitz_dom/
web_fonts.rs

1//! Font loading records shared by CSS, script and resource delivery.
2
3use std::fmt;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
6
7use blitz_traits::net::Bytes;
8use blitz_traits::shell::ShellProvider;
9use cssparser::{Parser, ParserInput, Token, serialize_string};
10use style::font_face::Source;
11use style::parser::ParserContext;
12use style::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
13use style::stylesheets::{CssRule, CssRuleType, Origin, StylesheetInDocument};
14use style_traits::{ParsingMode, ToCss};
15use url::Url;
16
17use crate::BaseDocument;
18
19use super::FontFaceOverrides;
20
21pub struct WebFont {
22    pub id: usize,
23    pub(crate) overrides: FontFaceOverrides,
24    pub(crate) source: Option<Url>,
25    pub(crate) data: Option<Bytes>,
26    pub(crate) css: bool,
27    pub(crate) epoch: usize,
28    status: AtomicU8,
29    shell: Arc<dyn ShellProvider>,
30}
31
32impl fmt::Debug for WebFont {
33    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34        formatter
35            .debug_struct("WebFont")
36            .field("id", &self.id)
37            .field("family", &self.family())
38            .field("status", &self.status())
39            .finish()
40    }
41}
42
43impl WebFont {
44    pub(crate) fn new(
45        overrides: FontFaceOverrides,
46        source: Option<Url>,
47        data: Option<Bytes>,
48        css: bool,
49        epoch: usize,
50        shell: Arc<dyn ShellProvider>,
51    ) -> Arc<Self> {
52        static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
53        Arc::new(Self {
54            id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
55            overrides,
56            source,
57            data,
58            css,
59            epoch,
60            status: AtomicU8::new(if css { 1 } else { 0 }),
61            shell,
62        })
63    }
64
65    pub fn family(&self) -> &str {
66        self.overrides.family_name.as_deref().unwrap_or("")
67    }
68
69    pub fn style(&self) -> &'static str {
70        use parley::fontique::FontStyle;
71        match self.overrides.style {
72            Some(FontStyle::Italic) => "italic",
73            Some(FontStyle::Oblique(_)) => "oblique",
74            _ => "normal",
75        }
76    }
77
78    pub fn weight(&self) -> f32 {
79        self.overrides.weight.unwrap_or(400.0)
80    }
81
82    pub fn status(&self) -> &'static str {
83        match self.status.load(Ordering::Acquire) {
84            0 => "unloaded",
85            1 => "loading",
86            2 => "loaded",
87            _ => "error",
88        }
89    }
90
91    pub(crate) fn start(&self) -> bool {
92        self.status
93            .compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
94            .is_ok()
95    }
96
97    pub(crate) fn finish(&self, success: bool) {
98        self.status
99            .store(if success { 2 } else { 3 }, Ordering::Release);
100        self.shell.request_redraw();
101    }
102}
103
104impl BaseDocument {
105    pub fn web_fonts(&self) -> &[Arc<WebFont>] {
106        &self.web_fonts
107    }
108
109    pub fn web_fonts_pending(&self) -> bool {
110        self.has_pending_critical_resources()
111            || self.web_fonts.iter().any(|face| face.status() == "loading")
112    }
113
114    pub(crate) fn reset_web_fonts(&mut self) {
115        self.font_epoch = self.font_epoch.wrapping_add(1);
116        for face in self.web_fonts.iter().filter(|face| face.css) {
117            if face.status() == "loading" {
118                face.finish(false);
119            }
120        }
121        self.web_fonts.retain(|face| !face.css);
122    }
123
124    pub fn make_web_font(
125        &self,
126        family: &str,
127        source: Option<&str>,
128        bytes: Option<Bytes>,
129        font_style: &str,
130        weight: &str,
131    ) -> Result<Arc<WebFont>, String> {
132        if family.is_empty() {
133            return Err(String::from("Empty font family"));
134        }
135        let mut quoted = String::new();
136        serialize_string(family, &mut quoted).expect("writing CSS cannot fail");
137        let css = format!(
138            "@font-face {{ font-family: {quoted}; src: {}; font-style: {font_style}; font-weight: {weight}; }}",
139            source.unwrap_or("url('about:blank')")
140        );
141        let sheet = self.make_stylesheet(css, Origin::Author);
142        let guard = self.guard.read();
143        let face = sheet
144            .contents(&guard)
145            .rules(&guard)
146            .iter()
147            .find_map(|rule| match rule {
148                CssRule::FontFace(face) => Some(face),
149                _ => None,
150            })
151            .ok_or_else(|| String::from("Invalid font descriptors"))?;
152        let descriptors = &face.read_with(&guard).descriptors;
153        let family = descriptors
154            .font_family
155            .as_ref()
156            .ok_or_else(|| String::from("Invalid font family"))?;
157        let style = descriptors
158            .font_style
159            .as_ref()
160            .ok_or_else(|| String::from("Invalid font style"))?;
161        let weight = descriptors
162            .font_weight
163            .as_ref()
164            .and_then(|weight| weight.0.compute())
165            .ok_or_else(|| String::from("Invalid font weight"))?;
166        let parsed_source = descriptors
167            .src
168            .as_ref()
169            .ok_or_else(|| String::from("Invalid font source"))?;
170        let url = if bytes.is_some() {
171            None
172        } else {
173            Some(
174                parsed_source
175                    .0
176                    .iter()
177                    .find_map(|source| match source {
178                        Source::Url(source) => source.url.url().map(|url| url.as_ref().clone()),
179                        Source::Local(_) => None,
180                    })
181                    .ok_or_else(|| String::from("A URL font source is required"))?,
182            )
183        };
184        Ok(WebFont::new(
185            FontFaceOverrides {
186                family_name: Some(family.name.to_string()),
187                weight: Some(weight.value()),
188                style: Some(super::stylo_to_fontique_style(style)),
189            },
190            url,
191            bytes,
192            false,
193            self.font_epoch,
194            self.shell_provider.clone(),
195        ))
196    }
197
198    pub fn load_web_font(&mut self, face: &Arc<WebFont>) {
199        super::load_script_font(self, face);
200    }
201
202    pub fn set_web_font_member(&mut self, face: &Arc<WebFont>, member: bool) -> bool {
203        let index = self.web_fonts.iter().position(|item| item.id == face.id);
204        if member {
205            if index.is_none() {
206                self.web_fonts.push(Arc::clone(face));
207            }
208            true
209        } else if face.css {
210            false
211        } else if let Some(index) = index {
212            self.web_fonts.remove(index);
213            self.invalidate_inline_contexts();
214            true
215        } else {
216            false
217        }
218    }
219
220    pub fn matching_web_fonts(&self, shorthand: &str) -> Result<Vec<Arc<WebFont>>, String> {
221        let url_data = self.url.url_extra_data();
222        let context = ParserContext::new(
223            Origin::Author,
224            &url_data,
225            Some(CssRuleType::Style),
226            ParsingMode::DEFAULT,
227            selectors::matching::QuirksMode::NoQuirks,
228            Default::default(),
229            None,
230            None,
231            Default::default(),
232        );
233        let property = PropertyId::parse_enabled_for_all_content("font")
234            .map_err(|_| String::from("Font shorthand is unavailable"))?;
235        let mut declarations = SourcePropertyDeclaration::default();
236        let mut input = ParserInput::new(shorthand);
237        Parser::new(&mut input)
238            .parse_entirely(|parser| {
239                PropertyDeclaration::parse_into(&mut declarations, property, &context, parser)
240            })
241            .map_err(|_| String::from("Invalid font shorthand"))?;
242
243        let mut families = String::new();
244        let mut font_style = String::from("normal");
245        let mut weight = 400.0_f32;
246        for declaration in declarations.declarations.drain(..) {
247            match declaration {
248                PropertyDeclaration::FontFamily(value) => families = value.to_css_string(),
249                PropertyDeclaration::FontStyle(value) => font_style = value.to_css_string(),
250                PropertyDeclaration::FontWeight(value) => {
251                    let text = value.to_css_string();
252                    weight = match text.as_str() {
253                        "normal" => 400.0,
254                        "bold" => 700.0,
255                        _ => text.parse().unwrap_or(400.0),
256                    };
257                }
258                _ => {}
259            }
260        }
261        if families.is_empty() {
262            return Err(String::from("A font family is required"));
263        }
264        let mut input = ParserInput::new(&families);
265        let mut parser = Parser::new(&mut input);
266        let mut names = Vec::new();
267        let mut name = String::new();
268        while !parser.is_exhausted() {
269            match parser
270                .next()
271                .map_err(|_| String::from("Invalid font family"))?
272            {
273                Token::Ident(value) | Token::QuotedString(value) => {
274                    if !name.is_empty() {
275                        name.push(' ');
276                    }
277                    name.push_str(value);
278                }
279                Token::Comma => {
280                    names.push(std::mem::take(&mut name));
281                }
282                _ => return Err(String::from("Invalid font family")),
283            }
284        }
285        names.push(name);
286        let style = if font_style.starts_with("italic") {
287            "italic"
288        } else if font_style.starts_with("oblique") {
289            "oblique"
290        } else {
291            "normal"
292        };
293        let mut matched = Vec::new();
294        for name in names {
295            let family: Vec<_> = self
296                .web_fonts
297                .iter()
298                .filter(|face| face.family().eq_ignore_ascii_case(&name))
299                .collect();
300            let has_style = family.iter().any(|face| face.style() == style);
301            let distance = family
302                .iter()
303                .filter(|face| !has_style || face.style() == style)
304                .map(|face| (face.weight() - weight).abs())
305                .min_by(f32::total_cmp);
306            if let Some(distance) = distance {
307                for face in family {
308                    if (!has_style || face.style() == style)
309                        && (face.weight() - weight).abs() == distance
310                        && !matched.iter().any(|item: &Arc<WebFont>| item.id == face.id)
311                    {
312                        matched.push(Arc::clone(face));
313                    }
314                }
315            }
316        }
317        Ok(matched)
318    }
319}