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_variable_fonts_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 };
636 let glyph_index = self.handle.glyph_index(codepoint);
637
638 let mut cache = self.cached_shape_data.write();
639 cache.glyph_indices.insert(codepoint, glyph_index);
640 glyph_index
641 }
642
643 pub fn has_glyph_for(&self, codepoint: char) -> bool {
644 self.glyph_index(codepoint).is_some()
645 }
646
647 pub(crate) fn glyph_h_kerning(
648 &self,
649 first_glyph: GlyphId,
650 second_glyph: GlyphId,
651 ) -> FractionalPixel {
652 self.handle.glyph_h_kerning(first_glyph, second_glyph)
653 }
654
655 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
656 {
657 let cache = self.cached_shape_data.read();
658 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
659 return *width;
660 }
661 }
662
663 let new_width = self
664 .handle
665 .glyph_h_advance(glyph_id)
666 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
667 let mut cache = self.cached_shape_data.write();
668 cache.glyph_advances.insert(glyph_id, new_width);
669 new_width
670 }
671
672 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
673 self.handle.typographic_bounds(glyph_id)
674 }
675
676 pub fn baseline(&self) -> Option<FontBaseline> {
678 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
679 }
680
681 #[cfg(not(target_os = "macos"))]
682 pub(crate) fn find_fallback_using_system_font_api(
683 &self,
684 _: &FallbackFontSelectionOptions,
685 ) -> Option<FontRef> {
686 None
687 }
688
689 fn get_family_name_from_font_data(&self) -> Result<Atom, NoUsableFamilyName> {
690 let name_table = self.table_for_tag(NAME).ok_or(NoUsableFamilyName)?;
691 let name_table = NameTable::read(read_fonts::FontData::new(name_table.buffer()))
692 .map_err(|_| NoUsableFamilyName)?;
693
694 let mut best_rank = -1;
700 let mut best_string = None;
701 for (index, name_record) in name_table
702 .name_record()
703 .iter()
704 .filter(|name_record| name_record.name_id() == NameId::FAMILY_NAME)
705 .enumerate()
706 {
707 let localized_string = LocalizedString::new(&name_table, name_record);
708 let rank = match (index, localized_string.language()) {
709 (_, Some("en-US")) => {
710 best_string = Some(localized_string);
711 break;
712 },
713 (_, Some("en")) => 2,
714 (_, None) => 1,
715 (0, _) => 0,
716 _ => continue,
717 };
718 if rank > best_rank {
719 best_rank = rank;
720 best_string = Some(localized_string);
721 }
722 }
723
724 best_string
725 .map(|best_string| best_string.chars().collect::<String>().into())
726 .ok_or(NoUsableFamilyName)
727 }
728
729 pub fn family_name(&self) -> Option<Atom> {
734 self.template
735 .font_face_rule()
736 .and_then(|font_face_rule| {
737 AtomicRef::filter_map(font_face_rule, |rule| rule.descriptors.font_family.as_ref())
738 })
739 .map(|font_family| font_family.name.clone())
740 .or_else(|| {
741 self.family_name
742 .get_or_init(|| self.get_family_name_from_font_data())
743 .clone()
744 .ok()
745 })
746 }
747}
748
749#[derive(Clone, Debug, MallocSizeOf)]
750pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
751
752impl PartialEq for FontRef {
753 fn eq(&self, other: &Self) -> bool {
754 Arc::ptr_eq(self, other)
755 }
756}
757
758impl Deref for FontRef {
759 type Target = Arc<Font>;
760 fn deref(&self) -> &Self::Target {
761 &self.0
762 }
763}
764
765#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
766pub struct FallbackKey {
767 script: Script,
768 unicode_block: Option<UnicodeBlock>,
769 language: Language,
770}
771
772impl FallbackKey {
773 fn new(options: &FallbackFontSelectionOptions) -> Self {
774 Self {
775 script: Script::from(options.character),
776 unicode_block: options.character.block(),
777 language: options.language,
778 }
779 }
780}
781
782#[derive(MallocSizeOf)]
786pub struct FontGroup {
787 descriptor: FontDescriptor,
790 families: SmallVec<[FontGroupFamily; 8]>,
793 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
798}
799
800impl FontGroup {
801 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
802 let families: SmallVec<[FontGroupFamily; 8]> = style
803 .font_family
804 .families
805 .iter()
806 .map(FontGroupFamily::local_or_web)
807 .collect();
808
809 FontGroup {
810 descriptor,
811 families,
812 fallbacks: Default::default(),
813 }
814 }
815
816 pub fn find_by_codepoint(
821 &self,
822 font_context: &FontContext,
823 codepoint: char,
824 next_codepoint: Option<char>,
825 language: Language,
826 ) -> Option<FontRef> {
827 let codepoint = match codepoint {
831 '\t' => ' ',
832 _ => codepoint,
833 };
834
835 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
836
837 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
838 options.character.is_ascii_lowercase();
839 let font_or_synthesized_small_caps = |font: FontRef| {
840 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
841 return font.synthesized_small_caps.clone();
842 }
843 Some(font)
844 };
845
846 let font_has_glyph_and_presentation = |font: &FontRef| {
847 match options.presentation_preference {
849 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
850 return false;
851 },
852 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
853 return false;
854 },
855 _ => {},
856 }
857 font.has_glyph_for(options.character)
858 };
859
860 let char_in_template =
861 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
862
863 if let Some(font) = self.find(
864 font_context,
865 &char_in_template,
866 &font_has_glyph_and_presentation,
867 ) {
868 return font_or_synthesized_small_caps(font);
869 }
870
871 let fallback_key = FallbackKey::new(&options);
872 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) &&
873 char_in_template(fallback.template.clone()) &&
874 font_has_glyph_and_presentation(fallback)
875 {
876 return font_or_synthesized_small_caps(fallback.clone());
877 }
878
879 if let Some(font) = self.find_fallback_using_system_font_list(
880 font_context,
881 options.clone(),
882 &char_in_template,
883 &font_has_glyph_and_presentation,
884 ) {
885 let fallback = font_or_synthesized_small_caps(font);
886 if let Some(fallback) = fallback.clone() {
887 self.fallbacks.write().insert(fallback_key, fallback);
888 }
889 return fallback;
890 }
891
892 let first_font = self.first(font_context);
893 if let Some(fallback) = first_font
894 .as_ref()
895 .and_then(|font| font.find_fallback_using_system_font_api(&options)) &&
896 font_has_glyph_and_presentation(&fallback)
897 {
898 return Some(fallback);
899 }
900
901 first_font
902 }
903
904 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
906 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
914 let font_predicate = |_: &FontRef| true;
915 self.find(font_context, &space_in_template, &font_predicate)
916 .or_else(|| {
917 self.find_fallback_using_system_font_list(
918 font_context,
919 FallbackFontSelectionOptions::default(),
920 &space_in_template,
921 &font_predicate,
922 )
923 })
924 }
925
926 fn find(
930 &self,
931 font_context: &FontContext,
932 template_predicate: &impl Fn(FontTemplateRef) -> bool,
933 font_predicate: &impl Fn(&FontRef) -> bool,
934 ) -> Option<FontRef> {
935 self.families
936 .iter()
937 .flat_map(|family| family.templates(font_context, &self.descriptor))
938 .find_map(|template| {
939 template.font_if_matches(
940 font_context,
941 &self.descriptor,
942 template_predicate,
943 font_predicate,
944 )
945 })
946 }
947
948 fn find_fallback_using_system_font_list(
954 &self,
955 font_context: &FontContext,
956 options: FallbackFontSelectionOptions,
957 template_predicate: &impl Fn(FontTemplateRef) -> bool,
958 font_predicate: &impl Fn(&FontRef) -> bool,
959 ) -> Option<FontRef> {
960 iter::once(FontFamilyDescriptor::default())
961 .chain(
962 fallback_font_families(options)
963 .into_iter()
964 .map(|family_name| {
965 let family = SingleFontFamily::FamilyName(FamilyName {
966 name: family_name.into(),
967 syntax: FontFamilyNameSyntax::Quoted,
968 });
969 FontFamilyDescriptor::new(family, FontSearchScope::Local)
970 }),
971 )
972 .find_map(|family_descriptor| {
973 FontGroupFamily::from(family_descriptor)
974 .templates(font_context, &self.descriptor)
975 .find_map(|template| {
976 template.font_if_matches(
977 font_context,
978 &self.descriptor,
979 template_predicate,
980 font_predicate,
981 )
982 })
983 })
984 }
985}
986
987#[derive(MallocSizeOf)]
994struct FontGroupFamilyTemplate {
995 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
996 template: FontTemplateRef,
997 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
998 font: OnceLock<Option<FontRef>>,
999}
1000
1001impl From<FontTemplateRef> for FontGroupFamilyTemplate {
1002 fn from(template: FontTemplateRef) -> Self {
1003 Self {
1004 template,
1005 font: Default::default(),
1006 }
1007 }
1008}
1009
1010impl FontGroupFamilyTemplate {
1011 fn font(
1012 &self,
1013 font_context: &FontContext,
1014 font_descriptor: &FontDescriptor,
1015 ) -> Option<FontRef> {
1016 self.font
1017 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
1018 .clone()
1019 }
1020
1021 fn font_if_matches(
1022 &self,
1023 font_context: &FontContext,
1024 font_descriptor: &FontDescriptor,
1025 template_predicate: &impl Fn(FontTemplateRef) -> bool,
1026 font_predicate: &impl Fn(&FontRef) -> bool,
1027 ) -> Option<FontRef> {
1028 if !template_predicate(self.template.clone()) {
1029 return None;
1030 }
1031 self.font(font_context, font_descriptor)
1032 .filter(font_predicate)
1033 }
1034}
1035
1036#[derive(MallocSizeOf)]
1041struct FontGroupFamily {
1042 family_descriptor: FontFamilyDescriptor,
1043 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
1044}
1045
1046impl From<FontFamilyDescriptor> for FontGroupFamily {
1047 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
1048 Self {
1049 family_descriptor,
1050 members: Default::default(),
1051 }
1052 }
1053}
1054
1055impl FontGroupFamily {
1056 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
1057 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
1058 }
1059
1060 fn templates(
1061 &self,
1062 font_context: &FontContext,
1063 font_descriptor: &FontDescriptor,
1064 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
1065 self.members
1066 .get_or_init(|| {
1067 font_context
1068 .matching_templates(font_descriptor, &self.family_descriptor)
1069 .into_iter()
1070 .map(Into::into)
1071 .collect()
1072 })
1073 .iter()
1074 }
1075}
1076
1077#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1079pub enum FontSearchScope {
1080 Any,
1082
1083 Local,
1085}
1086
1087#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1089pub struct FontFamilyDescriptor {
1090 pub(crate) family: SingleFontFamily,
1091 pub(crate) scope: FontSearchScope,
1092}
1093
1094impl FontFamilyDescriptor {
1095 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
1096 FontFamilyDescriptor { family, scope }
1097 }
1098
1099 fn default() -> FontFamilyDescriptor {
1100 FontFamilyDescriptor {
1101 family: SingleFontFamily::Generic(GenericFontFamily::None),
1102 scope: FontSearchScope::Local,
1103 }
1104 }
1105}
1106
1107pub struct FontBaseline {
1108 pub ideographic_baseline: f32,
1109 pub alphabetic_baseline: f32,
1110 pub hanging_baseline: f32,
1111}
1112
1113#[cfg(all(
1129 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
1130 not(target_env = "ohos")
1131))]
1132pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
1133 if value < mapping[0].0 {
1134 return mapping[0].1;
1135 }
1136
1137 for window in mapping.windows(2) {
1138 let (font_config_value_a, css_value_a) = window[0];
1139 let (font_config_value_b, css_value_b) = window[1];
1140
1141 if value >= font_config_value_a && value <= font_config_value_b {
1142 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
1143 return css_value_a + ((css_value_b - css_value_a) * ratio);
1144 }
1145 }
1146
1147 mapping[mapping.len() - 1].1
1148}
1149
1150fn compute_variations(
1152 descriptor: &FontDescriptor,
1153 template: &FontTemplateRef,
1154 variation_axes: VariationAxes,
1155) -> Vec<FontVariation> {
1156 let font_face_rule = template.font_face_rule();
1157
1158 let mut variations: Vec<FontVariation> = vec![];
1160
1161 let mut add_variation = |variation: FontVariation| {
1162 if !variations
1163 .iter()
1164 .any(|existing_variation| existing_variation.tag == variation.tag)
1165 {
1166 variations.push(variation);
1167 }
1168 };
1169
1170 descriptor
1174 .variation_settings
1175 .iter()
1176 .copied()
1177 .for_each(&mut add_variation);
1178
1179 if let Some(font_face_rule) = &font_face_rule {
1185 if let Some(variation_settings) =
1188 font_face_rule.descriptors.font_variation_settings.as_ref()
1189 {
1190 variation_settings
1191 .0
1192 .iter()
1193 .map(|variation| FontVariation {
1194 tag: variation.tag.0,
1195 value: variation.value.get().expect(
1196 "The value is enforced to be resolvable at parse time \
1197 (see FontVariationSettings::parse_for_font_face_rule).",
1198 ),
1199 })
1200 .for_each(&mut add_variation);
1201 }
1202 }
1203
1204 add_variation(FontVariation {
1215 tag: Tag::new(b"wght").to_u32(),
1216 value: descriptor.weight.value(),
1217 });
1218
1219 add_variation(FontVariation {
1220 tag: Tag::new(b"wdth").to_u32(),
1221 value: descriptor.width.0.to_float(),
1222 });
1223
1224 if variation_axes.intersects(VariationAxes::ITAL | VariationAxes::SLNT) {
1225 let clamped_font_style = font_face_rule
1226 .as_ref()
1227 .and_then(|font_face_rule| font_face_rule.descriptors.font_style.as_ref())
1228 .map(|font_style_range| {
1229 let computed_font_style_range =
1230 font_style_range.compute().expect("never returns None");
1231
1232 if computed_font_style_range.0 == FontStyle::ITALIC {
1233 debug_assert_eq!(computed_font_style_range.1, FontStyle::ITALIC);
1234 return FontStyle::ITALIC;
1235 }
1236 debug_assert_ne!(computed_font_style_range.1, FontStyle::ITALIC);
1237
1238 let specified_angle = if descriptor.style == FontStyle::ITALIC {
1239 FontStyle::DEFAULT_OBLIQUE_DEGREES as f32
1240 } else {
1241 descriptor.style.oblique_degrees()
1242 };
1243
1244 let clamped_angle = specified_angle
1245 .min(computed_font_style_range.1.oblique_degrees())
1246 .max(computed_font_style_range.0.oblique_degrees());
1247 FontStyle::oblique(clamped_angle)
1248 })
1249 .unwrap_or(descriptor.style);
1250
1251 let use_ital_axis = (variation_axes.contains(VariationAxes::ITAL) &&
1257 clamped_font_style == FontStyle::ITALIC) ||
1258 !variation_axes.contains(VariationAxes::SLNT);
1259 if use_ital_axis {
1260 add_variation(FontVariation {
1261 tag: ITAL.to_u32(),
1262 value: (clamped_font_style != FontStyle::NORMAL) as u32 as f32,
1263 });
1264 } else {
1265 if clamped_font_style == FontStyle::ITALIC {
1268 add_variation(FontVariation {
1269 tag: SLNT.to_u32(),
1270 value: (-FontStyle::DEFAULT_OBLIQUE_DEGREES) as f32,
1271 });
1272 } else {
1273 add_variation(FontVariation {
1274 tag: SLNT.to_u32(),
1275 value: -clamped_font_style.oblique_degrees(),
1276 });
1277 }
1278 }
1279 }
1280
1281 if descriptor.optical_sizing == FontOpticalSizing::Auto {
1283 add_variation(FontVariation {
1284 tag: Tag::new(b"opsz").to_u32(),
1285 value: descriptor.pt_size.to_f32_px(),
1286 });
1287 }
1288
1289 variations
1290}
1291
1292bitflags! {
1293 #[derive(Clone, Copy, Debug)]
1294 struct VariationAxes: u8 {
1295 const IS_VARIABLE = 1;
1297
1298 const ITAL = 1 << 1;
1302
1303 const SLNT = 1 << 2;
1307 }
1308}
1309
1310impl VariationAxes {
1311 fn from_platform_font(platform_font: &PlatformFont) -> Self {
1312 let Some(fvar_table) = platform_font.table_for_tag(FVAR) else {
1313 return VariationAxes::empty();
1315 };
1316
1317 let Some(variation_axes) = fvar_table
1318 .parse_as_specific_table::<Fvar<'_>>()
1319 .ok()
1320 .and_then(|fvar: Fvar<'_>| fvar.axis_instance_arrays().ok())
1321 .map(|instance_arrays| instance_arrays.axes())
1322 else {
1323 return VariationAxes::empty();
1325 };
1326
1327 if variation_axes.is_empty() {
1328 return VariationAxes::empty();
1329 }
1330
1331 let mut result = VariationAxes::IS_VARIABLE;
1332 for axis in variation_axes {
1333 match axis.axis_tag() {
1334 ITAL => result |= VariationAxes::ITAL,
1335 SLNT => result |= VariationAxes::SLNT,
1336 _ => {},
1337 }
1338 }
1339
1340 result
1341 }
1342}