1use std::borrow::ToOwned;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Deref;
9use std::sync::{Arc, OnceLock};
10use std::{iter, str};
11
12use app_units::Au;
13use atomic_refcell::AtomicRef;
14use bitflags::bitflags;
15use euclid::default::{Point2D, Rect};
16use euclid::num::Zero;
17use font_types::NameId;
18use fonts_traits::FontDescriptor;
19use icu_locale_core::subtags::Language;
20use icu_properties::props::{EnumeratedProperty, GeneralCategory};
21use log::debug;
22use malloc_size_of_derive::MallocSizeOf;
23use parking_lot::RwLock;
24use read_fonts::collections::int_set::Domain;
25use read_fonts::tables::fvar::Fvar;
26use read_fonts::tables::name::Name as NameTable;
27use read_fonts::tables::os2::{Os2, SelectionFlags};
28use read_fonts::types::Tag;
29use read_fonts::{FontRead, ReadError};
30use rustc_hash::FxHashMap;
31use serde::{Deserialize, Serialize};
32use servo_base::id::PainterId;
33use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
34use skrifa::string::LocalizedString;
35use smallvec::SmallVec;
36use style::Atom;
37use style::computed_values::font_optical_sizing::T as FontOpticalSizing;
38use style::computed_values::font_variant_caps;
39use style::computed_values::font_variant_position::T as FontVariantPosition;
40use style::properties::style_structs::Font as FontStyleStruct;
41use style::values::computed::font::{
42 FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
43};
44use style::values::computed::{
45 FontFeatureSettings, FontStyle, FontSynthesis, FontVariantEastAsian, FontVariantLigatures,
46 FontVariantNumeric, FontWeight, FontWidth,
47};
48use unicode_script::Script;
49use webrender_api::{
50 FontInstanceFlags, FontInstanceKey, FontInstancePlatformOptions, FontVariation,
51};
52
53use crate::font_feature_values::ResolvedFontVariantAlternates;
54use crate::platform::font::{FontTable, PlatformFont};
55use crate::platform::font_list::fallback_font_families;
56use crate::{
57 EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
58 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
59 FontTemplateRefMethods, GlyphId, LocalFontIdentifier, ShapedGlyph, ShapedText, Shaper,
60 compute_used_font_features,
61};
62
63pub(crate) const AFRC: Tag = Tag::new(b"afrc");
64pub(crate) const BASE: Tag = Tag::new(b"BASE");
65pub(crate) const CALT: Tag = Tag::new(b"calt");
66pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
67pub(crate) const CLIG: Tag = Tag::new(b"clig");
68pub(crate) const COLR: Tag = Tag::new(b"COLR");
69pub(crate) const CWSH: Tag = Tag::new(b"cwsh");
70pub(crate) const FRAC: Tag = Tag::new(b"frac");
71pub(crate) const DLIG: Tag = Tag::new(b"dlig");
72pub(crate) const FVAR: Tag = Tag::new(b"fvar");
73pub(crate) const FWID: Tag = Tag::new(b"fwid");
74pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
75pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
76pub(crate) const HIST: Tag = Tag::new(b"hist");
77pub(crate) const HLIG: Tag = Tag::new(b"hlig");
78pub(crate) const ITAL: Tag = Tag::new(b"ital");
79pub(crate) const JP04: Tag = Tag::new(b"jp04");
80pub(crate) const JP78: Tag = Tag::new(b"jp78");
81pub(crate) const JP83: Tag = Tag::new(b"jp83");
82pub(crate) const JP90: Tag = Tag::new(b"jp90");
83pub(crate) const KERN: Tag = Tag::new(b"kern");
84pub(crate) const LIGA: Tag = Tag::new(b"liga");
85pub(crate) const LNUM: Tag = Tag::new(b"lnum");
86pub(crate) const NALT: Tag = Tag::new(b"nalt");
87pub(crate) const NAME: Tag = Tag::new(b"name");
88pub(crate) const ONUM: Tag = Tag::new(b"onum");
89pub(crate) const ORNM: Tag = Tag::new(b"ornm");
90pub(crate) const ORDN: Tag = Tag::new(b"ordn");
91pub(crate) const PNUM: Tag = Tag::new(b"pnum");
92pub(crate) const PWID: Tag = Tag::new(b"pwid");
93pub(crate) const RUBY: Tag = Tag::new(b"ruby");
94pub(crate) const SALT: Tag = Tag::new(b"salt");
95pub(crate) const SBIX: Tag = Tag::new(b"sbix");
96pub(crate) const SLNT: Tag = Tag::new(b"slnt");
97pub(crate) const SMPL: Tag = Tag::new(b"smpl");
98pub(crate) const SUBS: Tag = Tag::new(b"subs");
99pub(crate) const SUPS: Tag = Tag::new(b"sups");
100pub(crate) const SWSH: Tag = Tag::new(b"swsh");
101pub(crate) const TNUM: Tag = Tag::new(b"tnum");
102pub(crate) const TRAD: Tag = Tag::new(b"trad");
103pub(crate) const ZERO: Tag = Tag::new(b"zero");
104
105pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
106
107pub trait PlatformFontMethods: Sized {
113 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
114 fn new_from_template(
115 template: FontTemplateRef,
116 pt_size: Option<Au>,
117 data: &Option<FontData>,
118 synthetic_bold: bool,
119 ) -> Result<PlatformFont, &'static str> {
120 let template = template.borrow();
121 let font_identifier = template.identifier.clone();
122
123 match font_identifier {
124 FontIdentifier::Local(font_identifier) => {
125 Self::new_from_local_font_identifier(font_identifier, pt_size, synthetic_bold)
126 },
127 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
128 font_identifier,
129 data.as_ref()
130 .expect("Should never create a web font without data."),
131 pt_size,
132 synthetic_bold,
133 ),
134 }
135 }
136
137 fn new_from_local_font_identifier(
138 font_identifier: LocalFontIdentifier,
139 pt_size: Option<Au>,
140 synthetic_bold: bool,
141 ) -> Result<PlatformFont, &'static str>;
142
143 fn new_from_data(
144 font_identifier: FontIdentifier,
145 data: &FontData,
146 pt_size: Option<Au>,
147 synthetic_bold: bool,
148 ) -> Result<PlatformFont, &'static str>;
149
150 fn copy_with_variations(
156 self,
157 _font_identifer: &FontIdentifier,
158 _variations: &[FontVariation],
159 ) -> Result<Self, &'static str>;
160
161 fn descriptor(&self) -> FontTemplateDescriptor;
164
165 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
166 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
167 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
168
169 fn metrics(&self) -> FontMetrics;
170 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
171 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
172
173 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
175
176 fn webrender_font_instance_platform_options(&self) -> FontInstancePlatformOptions {
178 Default::default()
179 }
180
181 fn variations(&self) -> &[FontVariation];
183
184 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
185 let mut style = FontStyle::NORMAL;
186 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
187 style = FontStyle::ITALIC;
188 }
189
190 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
191 let width = match os2.us_width_class() {
192 1 => FontWidth::ULTRA_CONDENSED,
193 2 => FontWidth::EXTRA_CONDENSED,
194 3 => FontWidth::CONDENSED,
195 4 => FontWidth::SEMI_CONDENSED,
196 5 => FontWidth::NORMAL,
197 6 => FontWidth::SEMI_EXPANDED,
198 7 => FontWidth::EXPANDED,
199 8 => FontWidth::EXTRA_EXPANDED,
200 9 => FontWidth::ULTRA_EXPANDED,
201 _ => FontWidth::NORMAL,
202 };
203
204 FontTemplateDescriptor::new(weight, width, style)
205 }
206}
207
208pub(crate) type FractionalPixel = f64;
210
211pub(crate) trait FontTableMethods {
212 fn buffer(&self) -> &[u8];
213 fn parse_as_specific_table<'a, Table>(&'a self) -> Result<Table, ReadError>
214 where
215 Table: FontRead<'a, Args = ()>,
216 {
217 Table::read(read_fonts::FontData::new(self.buffer()))
218 }
219}
220
221#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
222pub struct FontMetrics {
223 pub underline_size: Au,
224 pub underline_offset: Au,
225 pub strikeout_size: Au,
226 pub strikeout_offset: Au,
227 pub leading: Au,
228 pub x_height: Au,
229 pub em_size: Au,
230 pub ascent: Au,
231 pub descent: Au,
232 pub max_advance: Au,
233 pub average_advance: Au,
234 pub line_gap: Au,
235 pub zero_horizontal_advance: Option<Au>,
236 pub ic_horizontal_advance: Option<Au>,
237 pub space_advance: Au,
240}
241
242impl FontMetrics {
243 pub fn empty() -> Arc<Self> {
246 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
247 EMPTY.get_or_init(Default::default).clone()
248 }
249
250 pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
253 self.ascent != other.ascent ||
254 self.descent != other.descent ||
255 self.line_gap != other.line_gap
256 }
257}
258
259#[derive(Debug, Default)]
260struct CachedShapeData {
261 glyph_advances: FxHashMap<GlyphId, FractionalPixel>,
262 glyph_indices: FxHashMap<char, Option<GlyphId>>,
263 shaped_text: HashMap<ShapeCacheEntry, Arc<ShapedText>>,
264}
265
266impl malloc_size_of::MallocSizeOf for CachedShapeData {
267 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
268 let shaped_text_size = self
271 .shaped_text
272 .iter()
273 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
274 .sum::<usize>();
275 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
276 }
277}
278
279pub struct Font {
280 pub(crate) handle: PlatformFont,
281 pub(crate) template: FontTemplateRef,
282 pub metrics: OnceLock<Arc<FontMetrics>>,
283 pub descriptor: FontDescriptor,
284
285 data_and_index: OnceLock<FontDataAndIndex>,
288
289 shaper: OnceLock<Shaper>,
290 cached_shape_data: RwLock<CachedShapeData>,
291 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
292
293 pub(crate) synthesized_small_caps: Option<FontRef>,
297
298 has_color_bitmap_or_colr_table: OnceLock<bool>,
302
303 can_do_fast_shaping: OnceLock<bool>,
310
311 family_name: OnceLock<Result<Atom, NoUsableFamilyName>>,
316}
317
318#[derive(Clone)]
322struct NoUsableFamilyName;
323
324impl std::fmt::Debug for Font {
325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326 f.debug_struct("Font")
327 .field("template", &self.template)
328 .field("descriptor", &self.descriptor)
329 .finish()
330 }
331}
332
333impl malloc_size_of::MallocSizeOf for Font {
334 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
335 let metrics_size = self.metrics.get().map_or(0, |metrics| metrics.size_of(ops));
339 metrics_size +
340 self.descriptor.size_of(ops) +
341 self.cached_shape_data.read().size_of(ops) +
342 self.font_instance_key
343 .read()
344 .values()
345 .map(|key| key.size_of(ops))
346 .sum::<usize>()
347 }
348}
349
350impl Font {
351 pub fn new(
352 template: FontTemplateRef,
353 descriptor: FontDescriptor,
354 data: Option<FontData>,
355 synthesized_small_caps: Option<FontRef>,
356 ) -> Result<Font, &'static str> {
357 let synthetic_bold = {
358 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
359 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
360
361 is_bold && allows_synthetic_bold
362 };
363
364 let handle = PlatformFont::new_from_template(
365 template.clone(),
366 Some(descriptor.pt_size),
367 &data,
368 synthetic_bold,
369 )?;
370 let variation_axes = VariationAxes::from_platform_font(&handle);
371
372 let handle = if servo_config::pref!(layout_css_font_variations_enabled) &&
374 variation_axes.contains(VariationAxes::IS_VARIABLE)
375 {
376 let used_variations = compute_variations(&descriptor, &template, variation_axes);
377 handle.copy_with_variations(&template.identifier(), &used_variations)?
378 } else {
379 handle
380 };
381
382 Ok(Font {
383 handle,
384 template,
385 metrics: OnceLock::new(),
386 descriptor,
387 data_and_index: data
388 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
389 .unwrap_or_default(),
390 shaper: OnceLock::new(),
391 cached_shape_data: Default::default(),
392 font_instance_key: Default::default(),
393 synthesized_small_caps,
394 has_color_bitmap_or_colr_table: OnceLock::new(),
395 can_do_fast_shaping: OnceLock::new(),
396 family_name: Default::default(),
397 })
398 }
399
400 pub fn identifier(&self) -> AtomicRef<'_, FontIdentifier> {
402 self.template.identifier()
403 }
404
405 pub fn metrics(&self) -> &Arc<FontMetrics> {
406 self.metrics.get_or_init(|| Arc::new(self.handle.metrics()))
407 }
408
409 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
410 self.handle.webrender_font_instance_flags()
411 }
412
413 pub(crate) fn webrender_font_instance_platform_options(&self) -> FontInstancePlatformOptions {
414 self.handle.webrender_font_instance_platform_options()
415 }
416
417 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
418 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
419 self.table_for_tag(SBIX).is_some() ||
420 self.table_for_tag(CBDT).is_some() ||
421 self.table_for_tag(COLR).is_some()
422 })
423 }
424
425 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
426 *self
427 .font_instance_key
428 .write()
429 .entry(painter_id)
430 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
431 }
432
433 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
436 if let Some(data_and_index) = self.data_and_index.get() {
437 return Ok(data_and_index);
438 }
439
440 let FontIdentifier::Local(local_font_identifier) = &*self.identifier() else {
441 unreachable!("All web fonts should already have initialized data");
442 };
443 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
444 return Err(FontDataError::FailedToLoad);
445 };
446
447 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
448 Ok(data_and_index)
449 }
450
451 pub(crate) fn variations(&self) -> &[FontVariation] {
452 self.handle.variations()
453 }
454}
455
456bitflags! {
457 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
458 pub struct ShapingFlags: u8 {
459 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
461 const RTL_FLAG = 1 << 4;
463 const KEEP_ALL_FLAG = 1 << 5;
465 }
466}
467
468#[derive(Clone, Debug, Eq, Hash, PartialEq)]
470pub struct ShapingOptions {
471 pub letter_spacing: Au,
476 pub word_spacing: Au,
478 pub script: Script,
480 pub language: Language,
482 pub ligatures: FontVariantLigatures,
484 pub numeric: FontVariantNumeric,
486 pub east_asian: FontVariantEastAsian,
488 pub feature_settings: FontFeatureSettings,
490 pub position: FontVariantPosition,
492 pub alternates: ResolvedFontVariantAlternates,
494 pub flags: ShapingFlags,
496}
497
498impl ShapingOptions {
499 pub(crate) fn letter_spacing_for_character(&self, character: char) -> Au {
500 if GeneralCategory::for_char(character) == GeneralCategory::Format {
504 return Au::zero();
505 }
506 self.letter_spacing
507 }
508}
509
510#[derive(Clone, Debug, Eq, Hash, PartialEq)]
512struct ShapeCacheEntry {
513 text: String,
514 letter_spacing: Au,
515 word_spacing: Au,
516 script: Script,
517 language: Language,
518 font_features: Box<[(Tag, u32)]>,
519 flags: ShapingFlags,
520}
521
522impl Font {
523 #[servo_tracing::instrument(name = "Font::shape_text", skip_all)]
524 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<ShapedText> {
525 let font_features =
526 compute_used_font_features(options, self.template.borrow().font_face_rule.as_deref())
527 .collect();
528 let lookup_key = ShapeCacheEntry {
529 text: text.to_owned(),
530 letter_spacing: options.letter_spacing,
531 word_spacing: options.word_spacing,
532 script: options.script,
533 language: options.language,
534 flags: options.flags,
535 font_features,
536 };
537
538 if let Some(shaped_text) = self.cached_shape_data.read().shaped_text.get(&lookup_key) {
539 return shaped_text.clone();
540 }
541
542 let glyphs = if self.can_do_fast_shaping(text, options) {
543 debug!("shape_text: Using ASCII fast path.");
544 self.shape_text_fast(text, options)
545 } else {
546 debug!("shape_text: Using Harfbuzz.");
547 self.shaper.get_or_init(|| Shaper::new(self)).shape_text(
548 text,
549 options,
550 &lookup_key.font_features,
551 )
552 };
553
554 let shaped_text = Arc::new(glyphs);
555 let mut cache = self.cached_shape_data.write();
556 cache.shaped_text.insert(lookup_key, shaped_text.clone());
557
558 shaped_text
559 }
560
561 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
566 options.script == Script::Latin &&
567 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
568 *self.can_do_fast_shaping.get_or_init(|| {
569 self.table_for_tag(KERN).is_some() &&
570 self.table_for_tag(GPOS).is_none() &&
571 self.table_for_tag(GSUB).is_none()
572 }) &&
573 text.is_ascii()
574 }
575
576 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> ShapedText {
578 let mut glyph_store = ShapedText::new(text.len(), false );
579 let mut prev_glyph_id = None;
580 for (string_byte_offset, byte) in text.bytes().enumerate() {
581 let character = byte as char;
582 let Some(glyph_id) = self.glyph_index(character) else {
583 continue;
584 };
585
586 let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
587 let offset = prev_glyph_id.map(|prev| {
588 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
589 advance += h_kerning;
590 Point2D::new(h_kerning, Au::zero())
591 });
592
593 let mut glyph = ShapedGlyph {
594 glyph_id,
595 string_byte_offset,
596 advance,
597 offset,
598 };
599 glyph.adjust_for_character(character, options);
600
601 glyph_store.add_glyph(character, &glyph);
602 prev_glyph_id = Some(glyph_id);
603 }
604 glyph_store
605 }
606
607 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
608 let result = self.handle.table_for_tag(tag);
609 let status = if result.is_some() {
610 "Found"
611 } else {
612 "Didn't find"
613 };
614
615 debug!(
616 "{} font table[{}] in {:?},",
617 status,
618 str::from_utf8(tag.as_ref()).unwrap(),
619 self.identifier()
620 );
621 result
622 }
623
624 #[inline]
625 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
626 {
627 let cache = self.cached_shape_data.read();
628 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
629 return *glyph;
630 }
631 }
632 let codepoint = match self.descriptor.variant {
633 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
634 font_variant_caps::T::Normal => codepoint,
635 font_variant_caps::T::AllSmallCaps |
638 font_variant_caps::T::PetiteCaps |
639 font_variant_caps::T::AllPetiteCaps |
640 font_variant_caps::T::Unicase |
641 font_variant_caps::T::TitlingCaps => unreachable!(),
642 };
643 let glyph_index = self.handle.glyph_index(codepoint);
644
645 let mut cache = self.cached_shape_data.write();
646 cache.glyph_indices.insert(codepoint, glyph_index);
647 glyph_index
648 }
649
650 pub fn has_glyph_for(&self, codepoint: char) -> bool {
651 self.glyph_index(codepoint).is_some()
652 }
653
654 pub(crate) fn glyph_h_kerning(
655 &self,
656 first_glyph: GlyphId,
657 second_glyph: GlyphId,
658 ) -> FractionalPixel {
659 self.handle.glyph_h_kerning(first_glyph, second_glyph)
660 }
661
662 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
663 {
664 let cache = self.cached_shape_data.read();
665 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
666 return *width;
667 }
668 }
669
670 let new_width = self
671 .handle
672 .glyph_h_advance(glyph_id)
673 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
674 let mut cache = self.cached_shape_data.write();
675 cache.glyph_advances.insert(glyph_id, new_width);
676 new_width
677 }
678
679 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
680 self.handle.typographic_bounds(glyph_id)
681 }
682
683 pub fn baseline(&self) -> Option<FontBaseline> {
685 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
686 }
687
688 #[cfg(not(target_os = "macos"))]
689 pub(crate) fn find_fallback_using_system_font_api(
690 &self,
691 _: &FallbackFontSelectionOptions,
692 ) -> Option<FontRef> {
693 None
694 }
695
696 fn get_family_name_from_font_data(&self) -> Result<Atom, NoUsableFamilyName> {
697 let name_table = self.table_for_tag(NAME).ok_or(NoUsableFamilyName)?;
698 let name_table = NameTable::read(read_fonts::FontData::new(name_table.buffer()))
699 .map_err(|_| NoUsableFamilyName)?;
700
701 let mut best_rank = -1;
707 let mut best_string = None;
708 for (index, name_record) in name_table
709 .name_record()
710 .iter()
711 .filter(|name_record| name_record.name_id() == NameId::FAMILY_NAME)
712 .enumerate()
713 {
714 let localized_string = LocalizedString::new(&name_table, name_record);
715 let rank = match (index, localized_string.language()) {
716 (_, Some("en-US")) => {
717 best_string = Some(localized_string);
718 break;
719 },
720 (_, Some("en")) => 2,
721 (_, None) => 1,
722 (0, _) => 0,
723 _ => continue,
724 };
725 if rank > best_rank {
726 best_rank = rank;
727 best_string = Some(localized_string);
728 }
729 }
730
731 best_string
732 .map(|best_string| best_string.chars().collect::<String>().into())
733 .ok_or(NoUsableFamilyName)
734 }
735
736 pub fn family_name(&self) -> Option<Atom> {
741 self.template
742 .font_face_rule()
743 .and_then(|font_face_rule| {
744 AtomicRef::filter_map(font_face_rule, |rule| rule.descriptors.font_family.as_ref())
745 })
746 .map(|font_family| font_family.name.clone())
747 .or_else(|| {
748 self.family_name
749 .get_or_init(|| self.get_family_name_from_font_data())
750 .clone()
751 .ok()
752 })
753 }
754}
755
756#[derive(Clone, Debug, MallocSizeOf)]
757pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
758
759impl PartialEq for FontRef {
760 fn eq(&self, other: &Self) -> bool {
761 Arc::ptr_eq(self, other)
762 }
763}
764
765impl Deref for FontRef {
766 type Target = Arc<Font>;
767 fn deref(&self) -> &Self::Target {
768 &self.0
769 }
770}
771
772#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
773pub struct FallbackKey {
774 script: Script,
775 unicode_block: Option<UnicodeBlock>,
776 language: Language,
777}
778
779impl FallbackKey {
780 fn new(options: &FallbackFontSelectionOptions) -> Self {
781 Self {
782 script: Script::from(options.character),
783 unicode_block: options.character.block(),
784 language: options.language,
785 }
786 }
787}
788
789#[derive(MallocSizeOf)]
793pub struct FontGroup {
794 descriptor: FontDescriptor,
797 families: SmallVec<[FontGroupFamily; 8]>,
800 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
805}
806
807impl FontGroup {
808 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
809 let families: SmallVec<[FontGroupFamily; 8]> = style
810 .font_family
811 .families
812 .iter()
813 .map(FontGroupFamily::local_or_web)
814 .collect();
815
816 FontGroup {
817 descriptor,
818 families,
819 fallbacks: Default::default(),
820 }
821 }
822
823 pub fn find_by_codepoint(
828 &self,
829 font_context: &FontContext,
830 codepoint: char,
831 next_codepoint: Option<char>,
832 language: Language,
833 ) -> Option<FontRef> {
834 let codepoint = match codepoint {
838 '\t' => ' ',
839 _ => codepoint,
840 };
841
842 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
843
844 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
845 options.character.is_ascii_lowercase();
846 let font_or_synthesized_small_caps = |font: FontRef| {
847 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
848 return font.synthesized_small_caps.clone();
849 }
850 Some(font)
851 };
852
853 let font_has_glyph_and_presentation = |font: &FontRef| {
854 match options.presentation_preference {
856 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
857 return false;
858 },
859 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
860 return false;
861 },
862 _ => {},
863 }
864 font.has_glyph_for(options.character)
865 };
866
867 let char_in_template =
868 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
869
870 if let Some(font) = self.find(
871 font_context,
872 &char_in_template,
873 &font_has_glyph_and_presentation,
874 ) {
875 return font_or_synthesized_small_caps(font);
876 }
877
878 let fallback_key = FallbackKey::new(&options);
879 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) &&
880 char_in_template(fallback.template.clone()) &&
881 font_has_glyph_and_presentation(fallback)
882 {
883 return font_or_synthesized_small_caps(fallback.clone());
884 }
885
886 if let Some(font) = self.find_fallback_using_system_font_list(
887 font_context,
888 options.clone(),
889 &char_in_template,
890 &font_has_glyph_and_presentation,
891 ) {
892 let fallback = font_or_synthesized_small_caps(font);
893 if let Some(fallback) = fallback.clone() {
894 self.fallbacks.write().insert(fallback_key, fallback);
895 }
896 return fallback;
897 }
898
899 let first_font = self.first(font_context);
900 if let Some(fallback) = first_font
901 .as_ref()
902 .and_then(|font| font.find_fallback_using_system_font_api(&options)) &&
903 font_has_glyph_and_presentation(&fallback)
904 {
905 return Some(fallback);
906 }
907
908 first_font
909 }
910
911 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
913 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
921 let font_predicate = |_: &FontRef| true;
922 self.find(font_context, &space_in_template, &font_predicate)
923 .or_else(|| {
924 self.find_fallback_using_system_font_list(
925 font_context,
926 FallbackFontSelectionOptions::default(),
927 &space_in_template,
928 &font_predicate,
929 )
930 })
931 }
932
933 fn find(
937 &self,
938 font_context: &FontContext,
939 template_predicate: &impl Fn(FontTemplateRef) -> bool,
940 font_predicate: &impl Fn(&FontRef) -> bool,
941 ) -> Option<FontRef> {
942 self.families
943 .iter()
944 .flat_map(|family| family.templates(font_context, &self.descriptor))
945 .find_map(|template| {
946 template.font_if_matches(
947 font_context,
948 &self.descriptor,
949 template_predicate,
950 font_predicate,
951 )
952 })
953 }
954
955 fn find_fallback_using_system_font_list(
961 &self,
962 font_context: &FontContext,
963 options: FallbackFontSelectionOptions,
964 template_predicate: &impl Fn(FontTemplateRef) -> bool,
965 font_predicate: &impl Fn(&FontRef) -> bool,
966 ) -> Option<FontRef> {
967 iter::once(FontFamilyDescriptor::default())
968 .chain(
969 fallback_font_families(options)
970 .into_iter()
971 .map(|family_name| {
972 let family = SingleFontFamily::FamilyName(FamilyName {
973 name: family_name.into(),
974 syntax: FontFamilyNameSyntax::Quoted,
975 });
976 FontFamilyDescriptor::new(family, FontSearchScope::Local)
977 }),
978 )
979 .find_map(|family_descriptor| {
980 FontGroupFamily::from(family_descriptor)
981 .templates(font_context, &self.descriptor)
982 .find_map(|template| {
983 template.font_if_matches(
984 font_context,
985 &self.descriptor,
986 template_predicate,
987 font_predicate,
988 )
989 })
990 })
991 }
992}
993
994#[derive(MallocSizeOf)]
1001struct FontGroupFamilyTemplate {
1002 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
1003 template: FontTemplateRef,
1004 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
1005 font: OnceLock<Option<FontRef>>,
1006}
1007
1008impl From<FontTemplateRef> for FontGroupFamilyTemplate {
1009 fn from(template: FontTemplateRef) -> Self {
1010 Self {
1011 template,
1012 font: Default::default(),
1013 }
1014 }
1015}
1016
1017impl FontGroupFamilyTemplate {
1018 fn font(
1019 &self,
1020 font_context: &FontContext,
1021 font_descriptor: &FontDescriptor,
1022 ) -> Option<FontRef> {
1023 self.font
1024 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
1025 .clone()
1026 }
1027
1028 fn font_if_matches(
1029 &self,
1030 font_context: &FontContext,
1031 font_descriptor: &FontDescriptor,
1032 template_predicate: &impl Fn(FontTemplateRef) -> bool,
1033 font_predicate: &impl Fn(&FontRef) -> bool,
1034 ) -> Option<FontRef> {
1035 if !template_predicate(self.template.clone()) {
1036 return None;
1037 }
1038 self.font(font_context, font_descriptor)
1039 .filter(font_predicate)
1040 }
1041}
1042
1043#[derive(MallocSizeOf)]
1048struct FontGroupFamily {
1049 family_descriptor: FontFamilyDescriptor,
1050 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
1051}
1052
1053impl From<FontFamilyDescriptor> for FontGroupFamily {
1054 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
1055 Self {
1056 family_descriptor,
1057 members: Default::default(),
1058 }
1059 }
1060}
1061
1062impl FontGroupFamily {
1063 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
1064 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
1065 }
1066
1067 fn templates(
1068 &self,
1069 font_context: &FontContext,
1070 font_descriptor: &FontDescriptor,
1071 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
1072 self.members
1073 .get_or_init(|| {
1074 font_context
1075 .matching_templates(font_descriptor, &self.family_descriptor)
1076 .into_iter()
1077 .map(Into::into)
1078 .collect()
1079 })
1080 .iter()
1081 }
1082}
1083
1084#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1086pub enum FontSearchScope {
1087 Any,
1089
1090 Local,
1092}
1093
1094#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1096pub struct FontFamilyDescriptor {
1097 pub(crate) family: SingleFontFamily,
1098 pub(crate) scope: FontSearchScope,
1099}
1100
1101impl FontFamilyDescriptor {
1102 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
1103 FontFamilyDescriptor { family, scope }
1104 }
1105
1106 fn default() -> FontFamilyDescriptor {
1107 FontFamilyDescriptor {
1108 family: SingleFontFamily::Generic(GenericFontFamily::None),
1109 scope: FontSearchScope::Local,
1110 }
1111 }
1112}
1113
1114pub struct FontBaseline {
1115 pub ideographic_baseline: f32,
1116 pub alphabetic_baseline: f32,
1117 pub hanging_baseline: f32,
1118}
1119
1120#[cfg(all(
1136 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
1137 not(target_env = "ohos")
1138))]
1139pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
1140 if value < mapping[0].0 {
1141 return mapping[0].1;
1142 }
1143
1144 for window in mapping.windows(2) {
1145 let (font_config_value_a, css_value_a) = window[0];
1146 let (font_config_value_b, css_value_b) = window[1];
1147
1148 if value >= font_config_value_a && value <= font_config_value_b {
1149 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
1150 return css_value_a + ((css_value_b - css_value_a) * ratio);
1151 }
1152 }
1153
1154 mapping[mapping.len() - 1].1
1155}
1156
1157fn compute_variations(
1159 descriptor: &FontDescriptor,
1160 template: &FontTemplateRef,
1161 variation_axes: VariationAxes,
1162) -> Vec<FontVariation> {
1163 let font_face_rule = template.font_face_rule();
1164
1165 let mut variations: Vec<FontVariation> = vec![];
1167
1168 let mut add_variation = |variation: FontVariation| {
1169 if !variations
1170 .iter()
1171 .any(|existing_variation| existing_variation.tag == variation.tag)
1172 {
1173 variations.push(variation);
1174 }
1175 };
1176
1177 descriptor
1181 .variation_settings
1182 .iter()
1183 .copied()
1184 .for_each(&mut add_variation);
1185
1186 if let Some(font_face_rule) = &font_face_rule {
1192 if let Some(variation_settings) =
1195 font_face_rule.descriptors.font_variation_settings.as_ref()
1196 {
1197 variation_settings
1198 .0
1199 .iter()
1200 .map(|variation| FontVariation {
1201 tag: variation.tag.0,
1202 value: variation.value.get().expect(
1203 "The value is enforced to be resolvable at parse time \
1204 (see FontVariationSettings::parse_for_font_face_rule).",
1205 ),
1206 })
1207 .for_each(&mut add_variation);
1208 }
1209 }
1210
1211 add_variation(FontVariation {
1222 tag: Tag::new(b"wght").to_u32(),
1223 value: descriptor.weight.value(),
1224 });
1225
1226 add_variation(FontVariation {
1227 tag: Tag::new(b"wdth").to_u32(),
1228 value: descriptor.width.0.to_float(),
1229 });
1230
1231 if variation_axes.intersects(VariationAxes::ITAL | VariationAxes::SLNT) {
1232 let clamped_font_style = font_face_rule
1233 .as_ref()
1234 .and_then(|font_face_rule| font_face_rule.descriptors.font_style.as_ref())
1235 .map(|font_style_range| {
1236 let computed_font_style_range =
1237 font_style_range.compute().expect("never returns None");
1238
1239 if computed_font_style_range.0 == FontStyle::ITALIC {
1240 debug_assert_eq!(computed_font_style_range.1, FontStyle::ITALIC);
1241 return FontStyle::ITALIC;
1242 }
1243 debug_assert_ne!(computed_font_style_range.1, FontStyle::ITALIC);
1244
1245 let specified_angle = if descriptor.style == FontStyle::ITALIC {
1246 FontStyle::DEFAULT_OBLIQUE_DEGREES as f32
1247 } else {
1248 descriptor.style.oblique_degrees()
1249 };
1250
1251 let clamped_angle = specified_angle
1252 .min(computed_font_style_range.1.oblique_degrees())
1253 .max(computed_font_style_range.0.oblique_degrees());
1254 FontStyle::oblique(clamped_angle)
1255 })
1256 .unwrap_or(descriptor.style);
1257
1258 let use_ital_axis = (variation_axes.contains(VariationAxes::ITAL) &&
1264 clamped_font_style == FontStyle::ITALIC) ||
1265 !variation_axes.contains(VariationAxes::SLNT);
1266 if use_ital_axis {
1267 add_variation(FontVariation {
1268 tag: ITAL.to_u32(),
1269 value: (clamped_font_style != FontStyle::NORMAL) as u32 as f32,
1270 });
1271 } else {
1272 if clamped_font_style == FontStyle::ITALIC {
1275 add_variation(FontVariation {
1276 tag: SLNT.to_u32(),
1277 value: (-FontStyle::DEFAULT_OBLIQUE_DEGREES) as f32,
1278 });
1279 } else {
1280 add_variation(FontVariation {
1281 tag: SLNT.to_u32(),
1282 value: -clamped_font_style.oblique_degrees(),
1283 });
1284 }
1285 }
1286 }
1287
1288 if descriptor.optical_sizing == FontOpticalSizing::Auto {
1290 add_variation(FontVariation {
1291 tag: Tag::new(b"opsz").to_u32(),
1292 value: descriptor.pt_size.to_f32_px(),
1293 });
1294 }
1295
1296 variations
1297}
1298
1299bitflags! {
1300 #[derive(Clone, Copy, Debug)]
1301 struct VariationAxes: u8 {
1302 const IS_VARIABLE = 1;
1304
1305 const ITAL = 1 << 1;
1309
1310 const SLNT = 1 << 2;
1314 }
1315}
1316
1317impl VariationAxes {
1318 fn from_platform_font(platform_font: &PlatformFont) -> Self {
1319 let Some(fvar_table) = platform_font.table_for_tag(FVAR) else {
1320 return VariationAxes::empty();
1322 };
1323
1324 let Some(variation_axes) = fvar_table
1325 .parse_as_specific_table::<Fvar<'_>>()
1326 .ok()
1327 .and_then(|fvar: Fvar<'_>| fvar.axis_instance_arrays().ok())
1328 .map(|instance_arrays| instance_arrays.axes())
1329 else {
1330 return VariationAxes::empty();
1332 };
1333
1334 if variation_axes.is_empty() {
1335 return VariationAxes::empty();
1336 }
1337
1338 let mut result = VariationAxes::IS_VARIABLE;
1339 for axis in variation_axes {
1340 match axis.axis_tag() {
1341 ITAL => result |= VariationAxes::ITAL,
1342 SLNT => result |= VariationAxes::SLNT,
1343 _ => {},
1344 }
1345 }
1346
1347 result
1348 }
1349}