1use 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}