1use std::borrow::ToOwned;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, OnceLock};
11use std::time::Instant;
12use std::{iter, str};
13
14use app_units::Au;
15use bitflags::bitflags;
16use euclid::default::{Point2D, Rect};
17use euclid::num::Zero;
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::tables::os2::{Os2, SelectionFlags};
24use read_fonts::types::Tag;
25use rustc_hash::FxHashMap;
26use serde::{Deserialize, Serialize};
27use servo_base::id::PainterId;
28use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
29use smallvec::SmallVec;
30use style::computed_values::font_variant_caps;
31use style::properties::style_structs::Font as FontStyleStruct;
32use style::values::computed::font::{
33 FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
34};
35use style::values::computed::{FontStretch, FontStyle, FontSynthesis, FontWeight};
36use unicode_script::Script;
37use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
38
39use crate::platform::font::{FontTable, PlatformFont};
40use crate::platform::font_list::fallback_font_families;
41use crate::{
42 EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
43 FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
44 FontTemplateRefMethods, GlyphId, LocalFontIdentifier, ShapedGlyph, ShapedText, Shaper,
45};
46
47pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
48pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
49pub(crate) const KERN: Tag = Tag::new(b"kern");
50pub(crate) const SBIX: Tag = Tag::new(b"sbix");
51pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
52pub(crate) const COLR: Tag = Tag::new(b"COLR");
53pub(crate) const BASE: Tag = Tag::new(b"BASE");
54pub(crate) const LIGA: Tag = Tag::new(b"liga");
55
56pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
57
58static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
60
61pub trait PlatformFontMethods: Sized {
67 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
68 fn new_from_template(
69 template: FontTemplateRef,
70 pt_size: Option<Au>,
71 variations: &[FontVariation],
72 data: &Option<FontData>,
73 synthetic_bold: bool,
74 ) -> Result<PlatformFont, &'static str> {
75 let template = template.borrow();
76 let font_identifier = template.identifier.clone();
77
78 match font_identifier {
79 FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
80 font_identifier,
81 pt_size,
82 variations,
83 synthetic_bold,
84 ),
85 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
86 font_identifier,
87 data.as_ref()
88 .expect("Should never create a web font without data."),
89 pt_size,
90 variations,
91 synthetic_bold,
92 ),
93 }
94 }
95
96 fn new_from_local_font_identifier(
97 font_identifier: LocalFontIdentifier,
98 pt_size: Option<Au>,
99 variations: &[FontVariation],
100 synthetic_bold: bool,
101 ) -> Result<PlatformFont, &'static str>;
102
103 fn new_from_data(
104 font_identifier: FontIdentifier,
105 data: &FontData,
106 pt_size: Option<Au>,
107 variations: &[FontVariation],
108 synthetic_bold: bool,
109 ) -> Result<PlatformFont, &'static str>;
110
111 fn descriptor(&self) -> FontTemplateDescriptor;
114
115 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
116 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
117 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
118
119 fn metrics(&self) -> FontMetrics;
120 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
121 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
122
123 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
125
126 fn variations(&self) -> &[FontVariation];
128
129 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
130 let mut style = FontStyle::NORMAL;
131 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
132 style = FontStyle::ITALIC;
133 }
134
135 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
136 let stretch = match os2.us_width_class() {
137 1 => FontStretch::ULTRA_CONDENSED,
138 2 => FontStretch::EXTRA_CONDENSED,
139 3 => FontStretch::CONDENSED,
140 4 => FontStretch::SEMI_CONDENSED,
141 5 => FontStretch::NORMAL,
142 6 => FontStretch::SEMI_EXPANDED,
143 7 => FontStretch::EXPANDED,
144 8 => FontStretch::EXTRA_EXPANDED,
145 9 => FontStretch::ULTRA_EXPANDED,
146 _ => FontStretch::NORMAL,
147 };
148
149 FontTemplateDescriptor::new(weight, stretch, style)
150 }
151}
152
153pub(crate) type FractionalPixel = f64;
155
156pub(crate) trait FontTableMethods {
157 fn buffer(&self) -> &[u8];
158}
159
160#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
161pub struct FontMetrics {
162 pub underline_size: Au,
163 pub underline_offset: Au,
164 pub strikeout_size: Au,
165 pub strikeout_offset: Au,
166 pub leading: Au,
167 pub x_height: Au,
168 pub em_size: Au,
169 pub ascent: Au,
170 pub descent: Au,
171 pub max_advance: Au,
172 pub average_advance: Au,
173 pub line_gap: Au,
174 pub zero_horizontal_advance: Option<Au>,
175 pub ic_horizontal_advance: Option<Au>,
176 pub space_advance: Au,
179}
180
181impl FontMetrics {
182 pub fn empty() -> Arc<Self> {
185 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
186 EMPTY.get_or_init(Default::default).clone()
187 }
188
189 pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
192 self.ascent != other.ascent ||
193 self.descent != other.descent ||
194 self.line_gap != other.line_gap
195 }
196}
197
198#[derive(Debug, Default)]
199struct CachedShapeData {
200 glyph_advances: HashMap<GlyphId, FractionalPixel>,
201 glyph_indices: HashMap<char, Option<GlyphId>>,
202 shaped_text: HashMap<ShapeCacheEntry, Arc<ShapedText>>,
203}
204
205impl malloc_size_of::MallocSizeOf for CachedShapeData {
206 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
207 let shaped_text_size = self
210 .shaped_text
211 .iter()
212 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
213 .sum::<usize>();
214 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
215 }
216}
217
218pub struct Font {
219 pub(crate) handle: PlatformFont,
220 pub(crate) template: FontTemplateRef,
221 pub metrics: Arc<FontMetrics>,
222 pub descriptor: FontDescriptor,
223
224 data_and_index: OnceLock<FontDataAndIndex>,
227
228 shaper: OnceLock<Shaper>,
229 cached_shape_data: RwLock<CachedShapeData>,
230 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
231
232 pub(crate) synthesized_small_caps: Option<FontRef>,
236
237 has_color_bitmap_or_colr_table: OnceLock<bool>,
241
242 can_do_fast_shaping: OnceLock<bool>,
249}
250
251impl std::fmt::Debug for Font {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 f.debug_struct("Font")
254 .field("template", &self.template)
255 .field("descriptor", &self.descriptor)
256 .finish()
257 }
258}
259
260impl malloc_size_of::MallocSizeOf for Font {
261 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
262 self.metrics.size_of(ops) +
266 self.descriptor.size_of(ops) +
267 self.cached_shape_data.read().size_of(ops) +
268 self.font_instance_key
269 .read()
270 .values()
271 .map(|key| key.size_of(ops))
272 .sum::<usize>()
273 }
274}
275
276impl Font {
277 pub fn new(
278 template: FontTemplateRef,
279 descriptor: FontDescriptor,
280 data: Option<FontData>,
281 synthesized_small_caps: Option<FontRef>,
282 ) -> Result<Font, &'static str> {
283 let synthetic_bold = {
284 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
285 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
286
287 is_bold && allows_synthetic_bold
288 };
289
290 let handle = PlatformFont::new_from_template(
291 template.clone(),
292 Some(descriptor.pt_size),
293 &descriptor.variation_settings,
294 &data,
295 synthetic_bold,
296 )?;
297 let metrics = Arc::new(handle.metrics());
298
299 Ok(Font {
300 handle,
301 template,
302 metrics,
303 descriptor,
304 data_and_index: data
305 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
306 .unwrap_or_default(),
307 shaper: OnceLock::new(),
308 cached_shape_data: Default::default(),
309 font_instance_key: Default::default(),
310 synthesized_small_caps,
311 has_color_bitmap_or_colr_table: OnceLock::new(),
312 can_do_fast_shaping: OnceLock::new(),
313 })
314 }
315
316 pub fn identifier(&self) -> FontIdentifier {
318 self.template.identifier()
319 }
320
321 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
322 self.handle.webrender_font_instance_flags()
323 }
324
325 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
326 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
327 self.table_for_tag(SBIX).is_some() ||
328 self.table_for_tag(CBDT).is_some() ||
329 self.table_for_tag(COLR).is_some()
330 })
331 }
332
333 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
334 *self
335 .font_instance_key
336 .write()
337 .entry(painter_id)
338 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
339 }
340
341 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
344 if let Some(data_and_index) = self.data_and_index.get() {
345 return Ok(data_and_index);
346 }
347
348 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
349 unreachable!("All web fonts should already have initialized data");
350 };
351 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
352 return Err(FontDataError::FailedToLoad);
353 };
354
355 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
356 Ok(data_and_index)
357 }
358
359 pub(crate) fn variations(&self) -> &[FontVariation] {
360 self.handle.variations()
361 }
362}
363
364bitflags! {
365 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
366 pub struct ShapingFlags: u8 {
367 const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
369 const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
371 const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
373 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
375 const RTL_FLAG = 1 << 4;
377 const KEEP_ALL_FLAG = 1 << 5;
379 }
380}
381
382#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
384pub struct ShapingOptions {
385 pub letter_spacing: Option<Au>,
391 pub word_spacing: Option<Au>,
393 pub script: Script,
395 pub language: Language,
397 pub flags: ShapingFlags,
399}
400
401impl ShapingOptions {
402 pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
403 self.letter_spacing.filter(|_| {
407 icu_properties::maps::general_category().get(character) !=
408 icu_properties::GeneralCategory::Format
409 })
410 }
411}
412
413#[derive(Clone, Debug, Eq, Hash, PartialEq)]
415struct ShapeCacheEntry {
416 text: String,
417 options: ShapingOptions,
418}
419
420impl Font {
421 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<ShapedText> {
422 let lookup_key = ShapeCacheEntry {
423 text: text.to_owned(),
424 options: *options,
425 };
426 {
427 let cache = self.cached_shape_data.read();
428 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
429 return shaped_text.clone();
430 }
431 }
432
433 let start_time = Instant::now();
434 let glyphs = if self.can_do_fast_shaping(text, options) {
435 debug!("shape_text: Using ASCII fast path.");
436 self.shape_text_fast(text, options)
437 } else {
438 debug!("shape_text: Using Harfbuzz.");
439 self.shaper
440 .get_or_init(|| Shaper::new(self))
441 .shape_text(text, options)
442 };
443
444 let shaped_text = Arc::new(glyphs);
445 let mut cache = self.cached_shape_data.write();
446 cache.shaped_text.insert(lookup_key, shaped_text.clone());
447
448 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
449 ((Instant::now() - start_time).as_nanos()) as usize,
450 Ordering::Relaxed,
451 );
452
453 shaped_text
454 }
455
456 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
461 options.script == Script::Latin &&
462 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
463 *self.can_do_fast_shaping.get_or_init(|| {
464 self.table_for_tag(KERN).is_some() &&
465 self.table_for_tag(GPOS).is_none() &&
466 self.table_for_tag(GSUB).is_none()
467 }) &&
468 text.is_ascii()
469 }
470
471 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> ShapedText {
473 let mut glyph_store = ShapedText::new(text.len(), options);
474 let mut prev_glyph_id = None;
475 for (string_byte_offset, byte) in text.bytes().enumerate() {
476 let character = byte as char;
477 let Some(glyph_id) = self.glyph_index(character) else {
478 continue;
479 };
480
481 let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
482 let offset = prev_glyph_id.map(|prev| {
483 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
484 advance += h_kerning;
485 Point2D::new(h_kerning, Au::zero())
486 });
487
488 let mut glyph = ShapedGlyph {
489 glyph_id,
490 string_byte_offset,
491 advance,
492 offset,
493 };
494 glyph.adjust_for_character(character, options);
495
496 glyph_store.add_glyph(character, &glyph);
497 prev_glyph_id = Some(glyph_id);
498 }
499 glyph_store
500 }
501
502 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
503 let result = self.handle.table_for_tag(tag);
504 let status = if result.is_some() {
505 "Found"
506 } else {
507 "Didn't find"
508 };
509
510 debug!(
511 "{} font table[{}] in {:?},",
512 status,
513 str::from_utf8(tag.as_ref()).unwrap(),
514 self.identifier()
515 );
516 result
517 }
518
519 #[inline]
520 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
521 {
522 let cache = self.cached_shape_data.read();
523 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
524 return *glyph;
525 }
526 }
527 let codepoint = match self.descriptor.variant {
528 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
529 font_variant_caps::T::Normal => codepoint,
530 };
531 let glyph_index = self.handle.glyph_index(codepoint);
532
533 let mut cache = self.cached_shape_data.write();
534 cache.glyph_indices.insert(codepoint, glyph_index);
535 glyph_index
536 }
537
538 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
539 self.glyph_index(codepoint).is_some()
540 }
541
542 pub(crate) fn glyph_h_kerning(
543 &self,
544 first_glyph: GlyphId,
545 second_glyph: GlyphId,
546 ) -> FractionalPixel {
547 self.handle.glyph_h_kerning(first_glyph, second_glyph)
548 }
549
550 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
551 {
552 let cache = self.cached_shape_data.read();
553 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
554 return *width;
555 }
556 }
557
558 let new_width = self
559 .handle
560 .glyph_h_advance(glyph_id)
561 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
562 let mut cache = self.cached_shape_data.write();
563 cache.glyph_advances.insert(glyph_id, new_width);
564 new_width
565 }
566
567 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
568 self.handle.typographic_bounds(glyph_id)
569 }
570
571 pub fn baseline(&self) -> Option<FontBaseline> {
573 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
574 }
575
576 #[cfg(not(target_os = "macos"))]
577 pub(crate) fn find_fallback_using_system_font_api(
578 &self,
579 _: &FallbackFontSelectionOptions,
580 ) -> Option<FontRef> {
581 None
582 }
583}
584
585#[derive(Clone, Debug, MallocSizeOf)]
586pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
587
588impl Deref for FontRef {
589 type Target = Arc<Font>;
590 fn deref(&self) -> &Self::Target {
591 &self.0
592 }
593}
594
595#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
596pub struct FallbackKey {
597 script: Script,
598 unicode_block: Option<UnicodeBlock>,
599 language: Language,
600}
601
602impl FallbackKey {
603 fn new(options: &FallbackFontSelectionOptions) -> Self {
604 Self {
605 script: Script::from(options.character),
606 unicode_block: options.character.block(),
607 language: options.language,
608 }
609 }
610}
611
612#[derive(MallocSizeOf)]
616pub struct FontGroup {
617 descriptor: FontDescriptor,
620 families: SmallVec<[FontGroupFamily; 8]>,
623 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
628}
629
630impl FontGroup {
631 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
632 let families: SmallVec<[FontGroupFamily; 8]> = style
633 .font_family
634 .families
635 .iter()
636 .map(FontGroupFamily::local_or_web)
637 .collect();
638
639 FontGroup {
640 descriptor,
641 families,
642 fallbacks: Default::default(),
643 }
644 }
645
646 pub fn find_by_codepoint(
651 &self,
652 font_context: &FontContext,
653 codepoint: char,
654 next_codepoint: Option<char>,
655 language: Language,
656 ) -> Option<FontRef> {
657 let codepoint = match codepoint {
661 '\t' => ' ',
662 _ => codepoint,
663 };
664
665 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
666
667 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
668 options.character.is_ascii_lowercase();
669 let font_or_synthesized_small_caps = |font: FontRef| {
670 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
671 return font.synthesized_small_caps.clone();
672 }
673 Some(font)
674 };
675
676 let font_has_glyph_and_presentation = |font: &FontRef| {
677 match options.presentation_preference {
679 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
680 return false;
681 },
682 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
683 return false;
684 },
685 _ => {},
686 }
687 font.has_glyph_for(options.character)
688 };
689
690 let char_in_template =
691 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
692
693 if let Some(font) = self.find(
694 font_context,
695 &char_in_template,
696 &font_has_glyph_and_presentation,
697 ) {
698 return font_or_synthesized_small_caps(font);
699 }
700
701 let fallback_key = FallbackKey::new(&options);
702 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) {
703 if char_in_template(fallback.template.clone()) &&
704 font_has_glyph_and_presentation(fallback)
705 {
706 return font_or_synthesized_small_caps(fallback.clone());
707 }
708 }
709
710 if let Some(font) = self.find_fallback_using_system_font_list(
711 font_context,
712 options.clone(),
713 &char_in_template,
714 &font_has_glyph_and_presentation,
715 ) {
716 let fallback = font_or_synthesized_small_caps(font);
717 if let Some(fallback) = fallback.clone() {
718 self.fallbacks.write().insert(fallback_key, fallback);
719 }
720 return fallback;
721 }
722
723 let first_font = self.first(font_context);
724 if let Some(fallback) = first_font
725 .as_ref()
726 .and_then(|font| font.find_fallback_using_system_font_api(&options))
727 {
728 if font_has_glyph_and_presentation(&fallback) {
729 return Some(fallback);
730 }
731 }
732
733 first_font
734 }
735
736 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
738 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
746 let font_predicate = |_: &FontRef| true;
747 self.find(font_context, &space_in_template, &font_predicate)
748 .or_else(|| {
749 self.find_fallback_using_system_font_list(
750 font_context,
751 FallbackFontSelectionOptions::default(),
752 &space_in_template,
753 &font_predicate,
754 )
755 })
756 }
757
758 fn find(
762 &self,
763 font_context: &FontContext,
764 template_predicate: &impl Fn(FontTemplateRef) -> bool,
765 font_predicate: &impl Fn(&FontRef) -> bool,
766 ) -> Option<FontRef> {
767 self.families
768 .iter()
769 .flat_map(|family| family.templates(font_context, &self.descriptor))
770 .find_map(|template| {
771 template.font_if_matches(
772 font_context,
773 &self.descriptor,
774 template_predicate,
775 font_predicate,
776 )
777 })
778 }
779
780 fn find_fallback_using_system_font_list(
786 &self,
787 font_context: &FontContext,
788 options: FallbackFontSelectionOptions,
789 template_predicate: &impl Fn(FontTemplateRef) -> bool,
790 font_predicate: &impl Fn(&FontRef) -> bool,
791 ) -> Option<FontRef> {
792 iter::once(FontFamilyDescriptor::default())
793 .chain(
794 fallback_font_families(options)
795 .into_iter()
796 .map(|family_name| {
797 let family = SingleFontFamily::FamilyName(FamilyName {
798 name: family_name.into(),
799 syntax: FontFamilyNameSyntax::Quoted,
800 });
801 FontFamilyDescriptor::new(family, FontSearchScope::Local)
802 }),
803 )
804 .find_map(|family_descriptor| {
805 FontGroupFamily::from(family_descriptor)
806 .templates(font_context, &self.descriptor)
807 .find_map(|template| {
808 template.font_if_matches(
809 font_context,
810 &self.descriptor,
811 template_predicate,
812 font_predicate,
813 )
814 })
815 })
816 }
817}
818
819#[derive(MallocSizeOf)]
826struct FontGroupFamilyTemplate {
827 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
828 template: FontTemplateRef,
829 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
830 font: OnceLock<Option<FontRef>>,
831}
832
833impl From<FontTemplateRef> for FontGroupFamilyTemplate {
834 fn from(template: FontTemplateRef) -> Self {
835 Self {
836 template,
837 font: Default::default(),
838 }
839 }
840}
841
842impl FontGroupFamilyTemplate {
843 fn font(
844 &self,
845 font_context: &FontContext,
846 font_descriptor: &FontDescriptor,
847 ) -> Option<FontRef> {
848 self.font
849 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
850 .clone()
851 }
852
853 fn font_if_matches(
854 &self,
855 font_context: &FontContext,
856 font_descriptor: &FontDescriptor,
857 template_predicate: &impl Fn(FontTemplateRef) -> bool,
858 font_predicate: &impl Fn(&FontRef) -> bool,
859 ) -> Option<FontRef> {
860 if !template_predicate(self.template.clone()) {
861 return None;
862 }
863 self.font(font_context, font_descriptor)
864 .filter(font_predicate)
865 }
866}
867
868#[derive(MallocSizeOf)]
873struct FontGroupFamily {
874 family_descriptor: FontFamilyDescriptor,
875 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
876}
877
878impl From<FontFamilyDescriptor> for FontGroupFamily {
879 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
880 Self {
881 family_descriptor,
882 members: Default::default(),
883 }
884 }
885}
886
887impl FontGroupFamily {
888 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
889 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
890 }
891
892 fn templates(
893 &self,
894 font_context: &FontContext,
895 font_descriptor: &FontDescriptor,
896 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
897 self.members
898 .get_or_init(|| {
899 font_context
900 .matching_templates(font_descriptor, &self.family_descriptor)
901 .into_iter()
902 .map(Into::into)
903 .collect()
904 })
905 .iter()
906 }
907}
908
909#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
911pub enum FontSearchScope {
912 Any,
914
915 Local,
917}
918
919#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
921pub struct FontFamilyDescriptor {
922 pub(crate) family: SingleFontFamily,
923 pub(crate) scope: FontSearchScope,
924}
925
926impl FontFamilyDescriptor {
927 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
928 FontFamilyDescriptor { family, scope }
929 }
930
931 fn default() -> FontFamilyDescriptor {
932 FontFamilyDescriptor {
933 family: SingleFontFamily::Generic(GenericFontFamily::None),
934 scope: FontSearchScope::Local,
935 }
936 }
937}
938
939pub struct FontBaseline {
940 pub ideographic_baseline: f32,
941 pub alphabetic_baseline: f32,
942 pub hanging_baseline: f32,
943}
944
945#[cfg(all(
961 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
962 not(target_env = "ohos")
963))]
964pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
965 if value < mapping[0].0 {
966 return mapping[0].1;
967 }
968
969 for window in mapping.windows(2) {
970 let (font_config_value_a, css_value_a) = window[0];
971 let (font_config_value_b, css_value_b) = window[1];
972
973 if value >= font_config_value_a && value <= font_config_value_b {
974 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
975 return css_value_a + ((css_value_b - css_value_a) * ratio);
976 }
977 }
978
979 mapping[mapping.len() - 1].1
980}