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_locid::subtags::Language;
20use log::debug;
21use malloc_size_of_derive::MallocSizeOf;
22use parking_lot::RwLock;
23use read_fonts::FontRead;
24use read_fonts::tables::name::Name as NameTable;
25use read_fonts::tables::os2::{Os2, SelectionFlags};
26use read_fonts::types::Tag;
27use rustc_hash::FxHashMap;
28use serde::{Deserialize, Serialize};
29use servo_base::id::PainterId;
30use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
31use skrifa::string::LocalizedString;
32use smallvec::SmallVec;
33use style::Atom;
34use style::computed_values::font_variant_caps;
35use style::computed_values::font_variant_position::T as FontVariantPosition;
36use style::properties::style_structs::Font as FontStyleStruct;
37use style::values::computed::font::{
38 FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
39};
40use style::values::computed::{
41 FontFeatureSettings, FontStretch, FontStyle, FontSynthesis, FontVariantEastAsian,
42 FontVariantLigatures, FontVariantNumeric, FontWeight,
43};
44use unicode_script::Script;
45use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
46
47use crate::font_feature_values::ResolvedFontVariantAlternates;
48use crate::platform::font::{FontTable, PlatformFont};
49use crate::platform::font_list::fallback_font_families;
50use crate::{
51 EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
52 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
53 FontTemplateRefMethods, GlyphId, LocalFontIdentifier, ShapedGlyph, ShapedText, Shaper,
54 compute_used_font_features,
55};
56
57pub(crate) const AFRC: Tag = Tag::new(b"afrc");
58pub(crate) const BASE: Tag = Tag::new(b"BASE");
59pub(crate) const CALT: Tag = Tag::new(b"calt");
60pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
61pub(crate) const CLIG: Tag = Tag::new(b"clig");
62pub(crate) const COLR: Tag = Tag::new(b"COLR");
63pub(crate) const CWSH: Tag = Tag::new(b"cwsh");
64pub(crate) const FRAC: Tag = Tag::new(b"frac");
65pub(crate) const DLIG: Tag = Tag::new(b"dlig");
66pub(crate) const FWID: Tag = Tag::new(b"fwid");
67pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
68pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
69pub(crate) const HIST: Tag = Tag::new(b"hist");
70pub(crate) const HLIG: Tag = Tag::new(b"hlig");
71pub(crate) const JP04: Tag = Tag::new(b"jp04");
72pub(crate) const JP78: Tag = Tag::new(b"jp78");
73pub(crate) const JP83: Tag = Tag::new(b"jp83");
74pub(crate) const JP90: Tag = Tag::new(b"jp90");
75pub(crate) const KERN: Tag = Tag::new(b"kern");
76pub(crate) const LIGA: Tag = Tag::new(b"liga");
77pub(crate) const LNUM: Tag = Tag::new(b"lnum");
78pub(crate) const NALT: Tag = Tag::new(b"nalt");
79pub(crate) const NAME: Tag = Tag::new(b"name");
80pub(crate) const ONUM: Tag = Tag::new(b"onum");
81pub(crate) const ORNM: Tag = Tag::new(b"ornm");
82pub(crate) const ORDN: Tag = Tag::new(b"ordn");
83pub(crate) const PNUM: Tag = Tag::new(b"pnum");
84pub(crate) const PWID: Tag = Tag::new(b"pwid");
85pub(crate) const RUBY: Tag = Tag::new(b"ruby");
86pub(crate) const SALT: Tag = Tag::new(b"salt");
87pub(crate) const SBIX: Tag = Tag::new(b"sbix");
88pub(crate) const SMPL: Tag = Tag::new(b"smpl");
89pub(crate) const SUBS: Tag = Tag::new(b"subs");
90pub(crate) const SUPS: Tag = Tag::new(b"sups");
91pub(crate) const SWSH: Tag = Tag::new(b"swsh");
92pub(crate) const TNUM: Tag = Tag::new(b"tnum");
93pub(crate) const TRAD: Tag = Tag::new(b"trad");
94pub(crate) const ZERO: Tag = Tag::new(b"zero");
95
96pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
97
98pub trait PlatformFontMethods: Sized {
104 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
105 fn new_from_template(
106 template: FontTemplateRef,
107 pt_size: Option<Au>,
108 variations: &[FontVariation],
109 data: &Option<FontData>,
110 synthetic_bold: bool,
111 ) -> Result<PlatformFont, &'static str> {
112 let template = template.borrow();
113 let font_identifier = template.identifier.clone();
114
115 match font_identifier {
116 FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
117 font_identifier,
118 pt_size,
119 variations,
120 synthetic_bold,
121 ),
122 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
123 font_identifier,
124 data.as_ref()
125 .expect("Should never create a web font without data."),
126 pt_size,
127 variations,
128 synthetic_bold,
129 ),
130 }
131 }
132
133 fn new_from_local_font_identifier(
134 font_identifier: LocalFontIdentifier,
135 pt_size: Option<Au>,
136 variations: &[FontVariation],
137 synthetic_bold: bool,
138 ) -> Result<PlatformFont, &'static str>;
139
140 fn new_from_data(
141 font_identifier: FontIdentifier,
142 data: &FontData,
143 pt_size: Option<Au>,
144 variations: &[FontVariation],
145 synthetic_bold: bool,
146 ) -> Result<PlatformFont, &'static str>;
147
148 fn descriptor(&self) -> FontTemplateDescriptor;
151
152 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
153 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
154 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
155
156 fn metrics(&self) -> FontMetrics;
157 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
158 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
159
160 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
162
163 fn variations(&self) -> &[FontVariation];
165
166 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
167 let mut style = FontStyle::NORMAL;
168 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
169 style = FontStyle::ITALIC;
170 }
171
172 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
173 let stretch = match os2.us_width_class() {
174 1 => FontStretch::ULTRA_CONDENSED,
175 2 => FontStretch::EXTRA_CONDENSED,
176 3 => FontStretch::CONDENSED,
177 4 => FontStretch::SEMI_CONDENSED,
178 5 => FontStretch::NORMAL,
179 6 => FontStretch::SEMI_EXPANDED,
180 7 => FontStretch::EXPANDED,
181 8 => FontStretch::EXTRA_EXPANDED,
182 9 => FontStretch::ULTRA_EXPANDED,
183 _ => FontStretch::NORMAL,
184 };
185
186 FontTemplateDescriptor::new(weight, stretch, style)
187 }
188}
189
190pub(crate) type FractionalPixel = f64;
192
193pub(crate) trait FontTableMethods {
194 fn buffer(&self) -> &[u8];
195}
196
197#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
198pub struct FontMetrics {
199 pub underline_size: Au,
200 pub underline_offset: Au,
201 pub strikeout_size: Au,
202 pub strikeout_offset: Au,
203 pub leading: Au,
204 pub x_height: Au,
205 pub em_size: Au,
206 pub ascent: Au,
207 pub descent: Au,
208 pub max_advance: Au,
209 pub average_advance: Au,
210 pub line_gap: Au,
211 pub zero_horizontal_advance: Option<Au>,
212 pub ic_horizontal_advance: Option<Au>,
213 pub space_advance: Au,
216}
217
218impl FontMetrics {
219 pub fn empty() -> Arc<Self> {
222 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
223 EMPTY.get_or_init(Default::default).clone()
224 }
225
226 pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
229 self.ascent != other.ascent ||
230 self.descent != other.descent ||
231 self.line_gap != other.line_gap
232 }
233}
234
235#[derive(Debug, Default)]
236struct CachedShapeData {
237 glyph_advances: HashMap<GlyphId, FractionalPixel>,
238 glyph_indices: HashMap<char, Option<GlyphId>>,
239 shaped_text: HashMap<ShapeCacheEntry, Arc<ShapedText>>,
240}
241
242impl malloc_size_of::MallocSizeOf for CachedShapeData {
243 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
244 let shaped_text_size = self
247 .shaped_text
248 .iter()
249 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
250 .sum::<usize>();
251 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
252 }
253}
254
255pub struct Font {
256 pub(crate) handle: PlatformFont,
257 pub(crate) template: FontTemplateRef,
258 pub metrics: Arc<FontMetrics>,
259 pub descriptor: FontDescriptor,
260
261 data_and_index: OnceLock<FontDataAndIndex>,
264
265 shaper: OnceLock<Shaper>,
266 cached_shape_data: RwLock<CachedShapeData>,
267 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
268
269 pub(crate) synthesized_small_caps: Option<FontRef>,
273
274 has_color_bitmap_or_colr_table: OnceLock<bool>,
278
279 can_do_fast_shaping: OnceLock<bool>,
286
287 family_name: OnceLock<Result<Atom, NoUsableFamilyName>>,
292}
293
294#[derive(Clone)]
298struct NoUsableFamilyName;
299
300impl std::fmt::Debug for Font {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 f.debug_struct("Font")
303 .field("template", &self.template)
304 .field("descriptor", &self.descriptor)
305 .finish()
306 }
307}
308
309impl malloc_size_of::MallocSizeOf for Font {
310 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
311 self.metrics.size_of(ops) +
315 self.descriptor.size_of(ops) +
316 self.cached_shape_data.read().size_of(ops) +
317 self.font_instance_key
318 .read()
319 .values()
320 .map(|key| key.size_of(ops))
321 .sum::<usize>()
322 }
323}
324
325impl Font {
326 pub fn new(
327 template: FontTemplateRef,
328 descriptor: FontDescriptor,
329 data: Option<FontData>,
330 synthesized_small_caps: Option<FontRef>,
331 ) -> Result<Font, &'static str> {
332 let synthetic_bold = {
333 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
334 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
335
336 is_bold && allows_synthetic_bold
337 };
338
339 let handle = PlatformFont::new_from_template(
340 template.clone(),
341 Some(descriptor.pt_size),
342 &descriptor.variation_settings,
343 &data,
344 synthetic_bold,
345 )?;
346 let metrics = Arc::new(handle.metrics());
347
348 Ok(Font {
349 handle,
350 template,
351 metrics,
352 descriptor,
353 data_and_index: data
354 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
355 .unwrap_or_default(),
356 shaper: OnceLock::new(),
357 cached_shape_data: Default::default(),
358 font_instance_key: Default::default(),
359 synthesized_small_caps,
360 has_color_bitmap_or_colr_table: OnceLock::new(),
361 can_do_fast_shaping: OnceLock::new(),
362 family_name: Default::default(),
363 })
364 }
365
366 pub fn identifier(&self) -> FontIdentifier {
368 self.template.identifier()
369 }
370
371 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
372 self.handle.webrender_font_instance_flags()
373 }
374
375 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
376 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
377 self.table_for_tag(SBIX).is_some() ||
378 self.table_for_tag(CBDT).is_some() ||
379 self.table_for_tag(COLR).is_some()
380 })
381 }
382
383 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
384 *self
385 .font_instance_key
386 .write()
387 .entry(painter_id)
388 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
389 }
390
391 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
394 if let Some(data_and_index) = self.data_and_index.get() {
395 return Ok(data_and_index);
396 }
397
398 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
399 unreachable!("All web fonts should already have initialized data");
400 };
401 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
402 return Err(FontDataError::FailedToLoad);
403 };
404
405 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
406 Ok(data_and_index)
407 }
408
409 pub(crate) fn variations(&self) -> &[FontVariation] {
410 self.handle.variations()
411 }
412}
413
414bitflags! {
415 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
416 pub struct ShapingFlags: u8 {
417 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
419 const RTL_FLAG = 1 << 4;
421 const KEEP_ALL_FLAG = 1 << 5;
423 }
424}
425
426#[derive(Clone, Debug, Eq, Hash, PartialEq)]
428pub struct ShapingOptions {
429 pub letter_spacing: Option<Au>,
434 pub word_spacing: Option<Au>,
436 pub script: Script,
438 pub language: Language,
440 pub ligatures: FontVariantLigatures,
442 pub numeric: FontVariantNumeric,
444 pub east_asian: FontVariantEastAsian,
446 pub feature_settings: FontFeatureSettings,
448 pub position: FontVariantPosition,
450 pub alternates: ResolvedFontVariantAlternates,
452 pub flags: ShapingFlags,
454}
455
456impl ShapingOptions {
457 pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
458 self.letter_spacing.filter(|_| {
462 icu_properties::maps::general_category().get(character) !=
463 icu_properties::GeneralCategory::Format
464 })
465 }
466}
467
468#[derive(Clone, Debug, Eq, Hash, PartialEq)]
470struct ShapeCacheEntry {
471 text: String,
472 letter_spacing: Option<Au>,
473 word_spacing: Option<Au>,
474 script: Script,
475 language: Language,
476 font_features: Box<[(Tag, u32)]>,
477 flags: ShapingFlags,
478}
479
480impl Font {
481 #[servo_tracing::instrument(name = "Font::shape_text", skip_all)]
482 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<ShapedText> {
483 let font_features =
484 compute_used_font_features(options, self.template.borrow().font_face_rule.as_ref())
485 .collect();
486 let lookup_key = ShapeCacheEntry {
487 text: text.to_owned(),
488 letter_spacing: options.letter_spacing,
489 word_spacing: options.word_spacing,
490 script: options.script,
491 language: options.language,
492 flags: options.flags,
493 font_features,
494 };
495
496 if let Some(shaped_text) = self.cached_shape_data.read().shaped_text.get(&lookup_key) {
497 return shaped_text.clone();
498 }
499
500 let glyphs = if self.can_do_fast_shaping(text, options) {
501 debug!("shape_text: Using ASCII fast path.");
502 self.shape_text_fast(text, options)
503 } else {
504 debug!("shape_text: Using Harfbuzz.");
505 self.shaper.get_or_init(|| Shaper::new(self)).shape_text(
506 text,
507 options,
508 &lookup_key.font_features,
509 )
510 };
511
512 let shaped_text = Arc::new(glyphs);
513 let mut cache = self.cached_shape_data.write();
514 cache.shaped_text.insert(lookup_key, shaped_text.clone());
515
516 shaped_text
517 }
518
519 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
524 options.script == Script::Latin &&
525 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
526 *self.can_do_fast_shaping.get_or_init(|| {
527 self.table_for_tag(KERN).is_some() &&
528 self.table_for_tag(GPOS).is_none() &&
529 self.table_for_tag(GSUB).is_none()
530 }) &&
531 text.is_ascii()
532 }
533
534 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> ShapedText {
536 let mut glyph_store = ShapedText::new(text.len(), false );
537 let mut prev_glyph_id = None;
538 for (string_byte_offset, byte) in text.bytes().enumerate() {
539 let character = byte as char;
540 let Some(glyph_id) = self.glyph_index(character) else {
541 continue;
542 };
543
544 let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
545 let offset = prev_glyph_id.map(|prev| {
546 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
547 advance += h_kerning;
548 Point2D::new(h_kerning, Au::zero())
549 });
550
551 let mut glyph = ShapedGlyph {
552 glyph_id,
553 string_byte_offset,
554 advance,
555 offset,
556 };
557 glyph.adjust_for_character(character, options);
558
559 glyph_store.add_glyph(character, &glyph);
560 prev_glyph_id = Some(glyph_id);
561 }
562 glyph_store
563 }
564
565 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
566 let result = self.handle.table_for_tag(tag);
567 let status = if result.is_some() {
568 "Found"
569 } else {
570 "Didn't find"
571 };
572
573 debug!(
574 "{} font table[{}] in {:?},",
575 status,
576 str::from_utf8(tag.as_ref()).unwrap(),
577 self.identifier()
578 );
579 result
580 }
581
582 #[inline]
583 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
584 {
585 let cache = self.cached_shape_data.read();
586 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
587 return *glyph;
588 }
589 }
590 let codepoint = match self.descriptor.variant {
591 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
592 font_variant_caps::T::Normal => codepoint,
593 };
594 let glyph_index = self.handle.glyph_index(codepoint);
595
596 let mut cache = self.cached_shape_data.write();
597 cache.glyph_indices.insert(codepoint, glyph_index);
598 glyph_index
599 }
600
601 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
602 self.glyph_index(codepoint).is_some()
603 }
604
605 pub(crate) fn glyph_h_kerning(
606 &self,
607 first_glyph: GlyphId,
608 second_glyph: GlyphId,
609 ) -> FractionalPixel {
610 self.handle.glyph_h_kerning(first_glyph, second_glyph)
611 }
612
613 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
614 {
615 let cache = self.cached_shape_data.read();
616 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
617 return *width;
618 }
619 }
620
621 let new_width = self
622 .handle
623 .glyph_h_advance(glyph_id)
624 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
625 let mut cache = self.cached_shape_data.write();
626 cache.glyph_advances.insert(glyph_id, new_width);
627 new_width
628 }
629
630 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
631 self.handle.typographic_bounds(glyph_id)
632 }
633
634 pub fn baseline(&self) -> Option<FontBaseline> {
636 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
637 }
638
639 #[cfg(not(target_os = "macos"))]
640 pub(crate) fn find_fallback_using_system_font_api(
641 &self,
642 _: &FallbackFontSelectionOptions,
643 ) -> Option<FontRef> {
644 None
645 }
646
647 fn get_family_name_from_font_data(&self) -> Result<Atom, NoUsableFamilyName> {
648 let name_table = self.table_for_tag(NAME).ok_or(NoUsableFamilyName)?;
649 let name_table = NameTable::read(read_fonts::FontData::new(name_table.buffer()))
650 .map_err(|_| NoUsableFamilyName)?;
651
652 let mut best_rank = -1;
658 let mut best_string = None;
659 for (index, name_record) in name_table
660 .name_record()
661 .iter()
662 .filter(|name_record| name_record.name_id() == NameId::FAMILY_NAME)
663 .enumerate()
664 {
665 let localized_string = LocalizedString::new(&name_table, name_record);
666 let rank = match (index, localized_string.language()) {
667 (_, Some("en-US")) => {
668 best_string = Some(localized_string);
669 break;
670 },
671 (_, Some("en")) => 2,
672 (_, None) => 1,
673 (0, _) => 0,
674 _ => continue,
675 };
676 if rank > best_rank {
677 best_rank = rank;
678 best_string = Some(localized_string);
679 }
680 }
681
682 best_string
683 .map(|best_string| best_string.chars().collect::<String>().into())
684 .ok_or(NoUsableFamilyName)
685 }
686
687 pub fn family_name(&self) -> Option<Atom> {
692 self.template
693 .font_face_rule()
694 .and_then(|font_face_rule| {
695 AtomicRef::filter_map(font_face_rule, |rule| rule.font_family.as_ref())
696 })
697 .map(|font_family| font_family.name.clone())
698 .or_else(|| {
699 self.family_name
700 .get_or_init(|| self.get_family_name_from_font_data())
701 .clone()
702 .ok()
703 })
704 }
705}
706
707#[derive(Clone, Debug, MallocSizeOf)]
708pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
709
710impl PartialEq for FontRef {
711 fn eq(&self, other: &Self) -> bool {
712 Arc::ptr_eq(self, other)
713 }
714}
715
716impl Deref for FontRef {
717 type Target = Arc<Font>;
718 fn deref(&self) -> &Self::Target {
719 &self.0
720 }
721}
722
723#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
724pub struct FallbackKey {
725 script: Script,
726 unicode_block: Option<UnicodeBlock>,
727 language: Language,
728}
729
730impl FallbackKey {
731 fn new(options: &FallbackFontSelectionOptions) -> Self {
732 Self {
733 script: Script::from(options.character),
734 unicode_block: options.character.block(),
735 language: options.language,
736 }
737 }
738}
739
740#[derive(MallocSizeOf)]
744pub struct FontGroup {
745 descriptor: FontDescriptor,
748 families: SmallVec<[FontGroupFamily; 8]>,
751 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
756}
757
758impl FontGroup {
759 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
760 let families: SmallVec<[FontGroupFamily; 8]> = style
761 .font_family
762 .families
763 .iter()
764 .map(FontGroupFamily::local_or_web)
765 .collect();
766
767 FontGroup {
768 descriptor,
769 families,
770 fallbacks: Default::default(),
771 }
772 }
773
774 pub fn find_by_codepoint(
779 &self,
780 font_context: &FontContext,
781 codepoint: char,
782 next_codepoint: Option<char>,
783 language: Language,
784 ) -> Option<FontRef> {
785 let codepoint = match codepoint {
789 '\t' => ' ',
790 _ => codepoint,
791 };
792
793 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
794
795 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
796 options.character.is_ascii_lowercase();
797 let font_or_synthesized_small_caps = |font: FontRef| {
798 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
799 return font.synthesized_small_caps.clone();
800 }
801 Some(font)
802 };
803
804 let font_has_glyph_and_presentation = |font: &FontRef| {
805 match options.presentation_preference {
807 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
808 return false;
809 },
810 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
811 return false;
812 },
813 _ => {},
814 }
815 font.has_glyph_for(options.character)
816 };
817
818 let char_in_template =
819 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
820
821 if let Some(font) = self.find(
822 font_context,
823 &char_in_template,
824 &font_has_glyph_and_presentation,
825 ) {
826 return font_or_synthesized_small_caps(font);
827 }
828
829 let fallback_key = FallbackKey::new(&options);
830 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) &&
831 char_in_template(fallback.template.clone()) &&
832 font_has_glyph_and_presentation(fallback)
833 {
834 return font_or_synthesized_small_caps(fallback.clone());
835 }
836
837 if let Some(font) = self.find_fallback_using_system_font_list(
838 font_context,
839 options.clone(),
840 &char_in_template,
841 &font_has_glyph_and_presentation,
842 ) {
843 let fallback = font_or_synthesized_small_caps(font);
844 if let Some(fallback) = fallback.clone() {
845 self.fallbacks.write().insert(fallback_key, fallback);
846 }
847 return fallback;
848 }
849
850 let first_font = self.first(font_context);
851 if let Some(fallback) = first_font
852 .as_ref()
853 .and_then(|font| font.find_fallback_using_system_font_api(&options)) &&
854 font_has_glyph_and_presentation(&fallback)
855 {
856 return Some(fallback);
857 }
858
859 first_font
860 }
861
862 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
864 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
872 let font_predicate = |_: &FontRef| true;
873 self.find(font_context, &space_in_template, &font_predicate)
874 .or_else(|| {
875 self.find_fallback_using_system_font_list(
876 font_context,
877 FallbackFontSelectionOptions::default(),
878 &space_in_template,
879 &font_predicate,
880 )
881 })
882 }
883
884 fn find(
888 &self,
889 font_context: &FontContext,
890 template_predicate: &impl Fn(FontTemplateRef) -> bool,
891 font_predicate: &impl Fn(&FontRef) -> bool,
892 ) -> Option<FontRef> {
893 self.families
894 .iter()
895 .flat_map(|family| family.templates(font_context, &self.descriptor))
896 .find_map(|template| {
897 template.font_if_matches(
898 font_context,
899 &self.descriptor,
900 template_predicate,
901 font_predicate,
902 )
903 })
904 }
905
906 fn find_fallback_using_system_font_list(
912 &self,
913 font_context: &FontContext,
914 options: FallbackFontSelectionOptions,
915 template_predicate: &impl Fn(FontTemplateRef) -> bool,
916 font_predicate: &impl Fn(&FontRef) -> bool,
917 ) -> Option<FontRef> {
918 iter::once(FontFamilyDescriptor::default())
919 .chain(
920 fallback_font_families(options)
921 .into_iter()
922 .map(|family_name| {
923 let family = SingleFontFamily::FamilyName(FamilyName {
924 name: family_name.into(),
925 syntax: FontFamilyNameSyntax::Quoted,
926 });
927 FontFamilyDescriptor::new(family, FontSearchScope::Local)
928 }),
929 )
930 .find_map(|family_descriptor| {
931 FontGroupFamily::from(family_descriptor)
932 .templates(font_context, &self.descriptor)
933 .find_map(|template| {
934 template.font_if_matches(
935 font_context,
936 &self.descriptor,
937 template_predicate,
938 font_predicate,
939 )
940 })
941 })
942 }
943}
944
945#[derive(MallocSizeOf)]
952struct FontGroupFamilyTemplate {
953 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
954 template: FontTemplateRef,
955 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
956 font: OnceLock<Option<FontRef>>,
957}
958
959impl From<FontTemplateRef> for FontGroupFamilyTemplate {
960 fn from(template: FontTemplateRef) -> Self {
961 Self {
962 template,
963 font: Default::default(),
964 }
965 }
966}
967
968impl FontGroupFamilyTemplate {
969 fn font(
970 &self,
971 font_context: &FontContext,
972 font_descriptor: &FontDescriptor,
973 ) -> Option<FontRef> {
974 self.font
975 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
976 .clone()
977 }
978
979 fn font_if_matches(
980 &self,
981 font_context: &FontContext,
982 font_descriptor: &FontDescriptor,
983 template_predicate: &impl Fn(FontTemplateRef) -> bool,
984 font_predicate: &impl Fn(&FontRef) -> bool,
985 ) -> Option<FontRef> {
986 if !template_predicate(self.template.clone()) {
987 return None;
988 }
989 self.font(font_context, font_descriptor)
990 .filter(font_predicate)
991 }
992}
993
994#[derive(MallocSizeOf)]
999struct FontGroupFamily {
1000 family_descriptor: FontFamilyDescriptor,
1001 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
1002}
1003
1004impl From<FontFamilyDescriptor> for FontGroupFamily {
1005 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
1006 Self {
1007 family_descriptor,
1008 members: Default::default(),
1009 }
1010 }
1011}
1012
1013impl FontGroupFamily {
1014 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
1015 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
1016 }
1017
1018 fn templates(
1019 &self,
1020 font_context: &FontContext,
1021 font_descriptor: &FontDescriptor,
1022 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
1023 self.members
1024 .get_or_init(|| {
1025 font_context
1026 .matching_templates(font_descriptor, &self.family_descriptor)
1027 .into_iter()
1028 .map(Into::into)
1029 .collect()
1030 })
1031 .iter()
1032 }
1033}
1034
1035#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1037pub enum FontSearchScope {
1038 Any,
1040
1041 Local,
1043}
1044
1045#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
1047pub struct FontFamilyDescriptor {
1048 pub(crate) family: SingleFontFamily,
1049 pub(crate) scope: FontSearchScope,
1050}
1051
1052impl FontFamilyDescriptor {
1053 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
1054 FontFamilyDescriptor { family, scope }
1055 }
1056
1057 fn default() -> FontFamilyDescriptor {
1058 FontFamilyDescriptor {
1059 family: SingleFontFamily::Generic(GenericFontFamily::None),
1060 scope: FontSearchScope::Local,
1061 }
1062 }
1063}
1064
1065pub struct FontBaseline {
1066 pub ideographic_baseline: f32,
1067 pub alphabetic_baseline: f32,
1068 pub hanging_baseline: f32,
1069}
1070
1071#[cfg(all(
1087 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
1088 not(target_env = "ohos")
1089))]
1090pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
1091 if value < mapping[0].0 {
1092 return mapping[0].1;
1093 }
1094
1095 for window in mapping.windows(2) {
1096 let (font_config_value_a, css_value_a) = window[0];
1097 let (font_config_value_b, css_value_b) = window[1];
1098
1099 if value >= font_config_value_a && value <= font_config_value_b {
1100 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
1101 return css_value_a + ((css_value_b - css_value_a) * ratio);
1102 }
1103 }
1104
1105 mapping[mapping.len() - 1].1
1106}