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