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::{
36 FontStretch, FontStyle, FontSynthesis, 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};
48
49pub(crate) const AFRC: Tag = Tag::new(b"afrc");
50pub(crate) const BASE: Tag = Tag::new(b"BASE");
51pub(crate) const CALT: Tag = Tag::new(b"calt");
52pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
53pub(crate) const CLIG: Tag = Tag::new(b"clig");
54pub(crate) const COLR: Tag = Tag::new(b"COLR");
55pub(crate) const FRAC: Tag = Tag::new(b"frac");
56pub(crate) const DLIG: Tag = Tag::new(b"dlig");
57pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
58pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
59pub(crate) const HLIG: Tag = Tag::new(b"hlig");
60pub(crate) const KERN: Tag = Tag::new(b"kern");
61pub(crate) const LIGA: Tag = Tag::new(b"liga");
62pub(crate) const LNUM: Tag = Tag::new(b"lnum");
63pub(crate) const ONUM: Tag = Tag::new(b"onum");
64pub(crate) const ORDN: Tag = Tag::new(b"ordn");
65pub(crate) const PNUM: Tag = Tag::new(b"pnum");
66pub(crate) const SBIX: Tag = Tag::new(b"sbix");
67pub(crate) const TNUM: Tag = Tag::new(b"tnum");
68pub(crate) const ZERO: Tag = Tag::new(b"zero");
69
70pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
71
72static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
74
75pub trait PlatformFontMethods: Sized {
81 #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
82 fn new_from_template(
83 template: FontTemplateRef,
84 pt_size: Option<Au>,
85 variations: &[FontVariation],
86 data: &Option<FontData>,
87 synthetic_bold: bool,
88 ) -> Result<PlatformFont, &'static str> {
89 let template = template.borrow();
90 let font_identifier = template.identifier.clone();
91
92 match font_identifier {
93 FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
94 font_identifier,
95 pt_size,
96 variations,
97 synthetic_bold,
98 ),
99 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
100 font_identifier,
101 data.as_ref()
102 .expect("Should never create a web font without data."),
103 pt_size,
104 variations,
105 synthetic_bold,
106 ),
107 }
108 }
109
110 fn new_from_local_font_identifier(
111 font_identifier: LocalFontIdentifier,
112 pt_size: Option<Au>,
113 variations: &[FontVariation],
114 synthetic_bold: bool,
115 ) -> Result<PlatformFont, &'static str>;
116
117 fn new_from_data(
118 font_identifier: FontIdentifier,
119 data: &FontData,
120 pt_size: Option<Au>,
121 variations: &[FontVariation],
122 synthetic_bold: bool,
123 ) -> Result<PlatformFont, &'static str>;
124
125 fn descriptor(&self) -> FontTemplateDescriptor;
128
129 fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
130 fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
131 fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
132
133 fn metrics(&self) -> FontMetrics;
134 fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
135 fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
136
137 fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
139
140 fn variations(&self) -> &[FontVariation];
142
143 fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
144 let mut style = FontStyle::NORMAL;
145 if os2.fs_selection().contains(SelectionFlags::ITALIC) {
146 style = FontStyle::ITALIC;
147 }
148
149 let weight = FontWeight::from_float(os2.us_weight_class() as f32);
150 let stretch = match os2.us_width_class() {
151 1 => FontStretch::ULTRA_CONDENSED,
152 2 => FontStretch::EXTRA_CONDENSED,
153 3 => FontStretch::CONDENSED,
154 4 => FontStretch::SEMI_CONDENSED,
155 5 => FontStretch::NORMAL,
156 6 => FontStretch::SEMI_EXPANDED,
157 7 => FontStretch::EXPANDED,
158 8 => FontStretch::EXTRA_EXPANDED,
159 9 => FontStretch::ULTRA_EXPANDED,
160 _ => FontStretch::NORMAL,
161 };
162
163 FontTemplateDescriptor::new(weight, stretch, style)
164 }
165}
166
167pub(crate) type FractionalPixel = f64;
169
170pub(crate) trait FontTableMethods {
171 fn buffer(&self) -> &[u8];
172}
173
174#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
175pub struct FontMetrics {
176 pub underline_size: Au,
177 pub underline_offset: Au,
178 pub strikeout_size: Au,
179 pub strikeout_offset: Au,
180 pub leading: Au,
181 pub x_height: Au,
182 pub em_size: Au,
183 pub ascent: Au,
184 pub descent: Au,
185 pub max_advance: Au,
186 pub average_advance: Au,
187 pub line_gap: Au,
188 pub zero_horizontal_advance: Option<Au>,
189 pub ic_horizontal_advance: Option<Au>,
190 pub space_advance: Au,
193}
194
195impl FontMetrics {
196 pub fn empty() -> Arc<Self> {
199 static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
200 EMPTY.get_or_init(Default::default).clone()
201 }
202
203 pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
206 self.ascent != other.ascent ||
207 self.descent != other.descent ||
208 self.line_gap != other.line_gap
209 }
210}
211
212#[derive(Debug, Default)]
213struct CachedShapeData {
214 glyph_advances: HashMap<GlyphId, FractionalPixel>,
215 glyph_indices: HashMap<char, Option<GlyphId>>,
216 shaped_text: HashMap<ShapeCacheEntry, Arc<ShapedText>>,
217}
218
219impl malloc_size_of::MallocSizeOf for CachedShapeData {
220 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
221 let shaped_text_size = self
224 .shaped_text
225 .iter()
226 .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
227 .sum::<usize>();
228 self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
229 }
230}
231
232pub struct Font {
233 pub(crate) handle: PlatformFont,
234 pub(crate) template: FontTemplateRef,
235 pub metrics: Arc<FontMetrics>,
236 pub descriptor: FontDescriptor,
237
238 data_and_index: OnceLock<FontDataAndIndex>,
241
242 shaper: OnceLock<Shaper>,
243 cached_shape_data: RwLock<CachedShapeData>,
244 font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
245
246 pub(crate) synthesized_small_caps: Option<FontRef>,
250
251 has_color_bitmap_or_colr_table: OnceLock<bool>,
255
256 can_do_fast_shaping: OnceLock<bool>,
263}
264
265impl std::fmt::Debug for Font {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 f.debug_struct("Font")
268 .field("template", &self.template)
269 .field("descriptor", &self.descriptor)
270 .finish()
271 }
272}
273
274impl malloc_size_of::MallocSizeOf for Font {
275 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
276 self.metrics.size_of(ops) +
280 self.descriptor.size_of(ops) +
281 self.cached_shape_data.read().size_of(ops) +
282 self.font_instance_key
283 .read()
284 .values()
285 .map(|key| key.size_of(ops))
286 .sum::<usize>()
287 }
288}
289
290impl Font {
291 pub fn new(
292 template: FontTemplateRef,
293 descriptor: FontDescriptor,
294 data: Option<FontData>,
295 synthesized_small_caps: Option<FontRef>,
296 ) -> Result<Font, &'static str> {
297 let synthetic_bold = {
298 let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
299 let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
300
301 is_bold && allows_synthetic_bold
302 };
303
304 let handle = PlatformFont::new_from_template(
305 template.clone(),
306 Some(descriptor.pt_size),
307 &descriptor.variation_settings,
308 &data,
309 synthetic_bold,
310 )?;
311 let metrics = Arc::new(handle.metrics());
312
313 Ok(Font {
314 handle,
315 template,
316 metrics,
317 descriptor,
318 data_and_index: data
319 .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
320 .unwrap_or_default(),
321 shaper: OnceLock::new(),
322 cached_shape_data: Default::default(),
323 font_instance_key: Default::default(),
324 synthesized_small_caps,
325 has_color_bitmap_or_colr_table: OnceLock::new(),
326 can_do_fast_shaping: OnceLock::new(),
327 })
328 }
329
330 pub fn identifier(&self) -> FontIdentifier {
332 self.template.identifier()
333 }
334
335 pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
336 self.handle.webrender_font_instance_flags()
337 }
338
339 pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
340 *self.has_color_bitmap_or_colr_table.get_or_init(|| {
341 self.table_for_tag(SBIX).is_some() ||
342 self.table_for_tag(CBDT).is_some() ||
343 self.table_for_tag(COLR).is_some()
344 })
345 }
346
347 pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
348 *self
349 .font_instance_key
350 .write()
351 .entry(painter_id)
352 .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
353 }
354
355 pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
358 if let Some(data_and_index) = self.data_and_index.get() {
359 return Ok(data_and_index);
360 }
361
362 let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
363 unreachable!("All web fonts should already have initialized data");
364 };
365 let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
366 return Err(FontDataError::FailedToLoad);
367 };
368
369 let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
370 Ok(data_and_index)
371 }
372
373 pub(crate) fn variations(&self) -> &[FontVariation] {
374 self.handle.variations()
375 }
376}
377
378bitflags! {
379 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
380 pub struct ShapingFlags: u8 {
381 const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
383 const RTL_FLAG = 1 << 4;
385 const KEEP_ALL_FLAG = 1 << 5;
387 }
388}
389
390#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
392pub struct ShapingOptions {
393 pub letter_spacing: Option<Au>,
398 pub word_spacing: Option<Au>,
400 pub script: Script,
402 pub language: Language,
404 pub ligatures: FontVariantLigatures,
406 pub numeric: FontVariantNumeric,
408 pub flags: ShapingFlags,
410}
411
412impl ShapingOptions {
413 pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
414 self.letter_spacing.filter(|_| {
418 icu_properties::maps::general_category().get(character) !=
419 icu_properties::GeneralCategory::Format
420 })
421 }
422}
423
424#[derive(Clone, Debug, Eq, Hash, PartialEq)]
426struct ShapeCacheEntry {
427 text: String,
428 options: ShapingOptions,
429}
430
431impl Font {
432 pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<ShapedText> {
433 let lookup_key = ShapeCacheEntry {
434 text: text.to_owned(),
435 options: *options,
436 };
437 {
438 let cache = self.cached_shape_data.read();
439 if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
440 return shaped_text.clone();
441 }
442 }
443
444 let start_time = Instant::now();
445 let glyphs = if self.can_do_fast_shaping(text, options) {
446 debug!("shape_text: Using ASCII fast path.");
447 self.shape_text_fast(text, options)
448 } else {
449 debug!("shape_text: Using Harfbuzz.");
450 self.shaper
451 .get_or_init(|| Shaper::new(self))
452 .shape_text(text, options)
453 };
454
455 let shaped_text = Arc::new(glyphs);
456 let mut cache = self.cached_shape_data.write();
457 cache.shaped_text.insert(lookup_key, shaped_text.clone());
458
459 TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
460 ((Instant::now() - start_time).as_nanos()) as usize,
461 Ordering::Relaxed,
462 );
463
464 shaped_text
465 }
466
467 pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
472 options.script == Script::Latin &&
473 !options.flags.contains(ShapingFlags::RTL_FLAG) &&
474 *self.can_do_fast_shaping.get_or_init(|| {
475 self.table_for_tag(KERN).is_some() &&
476 self.table_for_tag(GPOS).is_none() &&
477 self.table_for_tag(GSUB).is_none()
478 }) &&
479 text.is_ascii()
480 }
481
482 fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> ShapedText {
484 let mut glyph_store = ShapedText::new(text.len(), false );
485 let mut prev_glyph_id = None;
486 for (string_byte_offset, byte) in text.bytes().enumerate() {
487 let character = byte as char;
488 let Some(glyph_id) = self.glyph_index(character) else {
489 continue;
490 };
491
492 let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
493 let offset = prev_glyph_id.map(|prev| {
494 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
495 advance += h_kerning;
496 Point2D::new(h_kerning, Au::zero())
497 });
498
499 let mut glyph = ShapedGlyph {
500 glyph_id,
501 string_byte_offset,
502 advance,
503 offset,
504 };
505 glyph.adjust_for_character(character, options);
506
507 glyph_store.add_glyph(character, &glyph);
508 prev_glyph_id = Some(glyph_id);
509 }
510 glyph_store
511 }
512
513 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
514 let result = self.handle.table_for_tag(tag);
515 let status = if result.is_some() {
516 "Found"
517 } else {
518 "Didn't find"
519 };
520
521 debug!(
522 "{} font table[{}] in {:?},",
523 status,
524 str::from_utf8(tag.as_ref()).unwrap(),
525 self.identifier()
526 );
527 result
528 }
529
530 #[inline]
531 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
532 {
533 let cache = self.cached_shape_data.read();
534 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
535 return *glyph;
536 }
537 }
538 let codepoint = match self.descriptor.variant {
539 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
540 font_variant_caps::T::Normal => codepoint,
541 };
542 let glyph_index = self.handle.glyph_index(codepoint);
543
544 let mut cache = self.cached_shape_data.write();
545 cache.glyph_indices.insert(codepoint, glyph_index);
546 glyph_index
547 }
548
549 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
550 self.glyph_index(codepoint).is_some()
551 }
552
553 pub(crate) fn glyph_h_kerning(
554 &self,
555 first_glyph: GlyphId,
556 second_glyph: GlyphId,
557 ) -> FractionalPixel {
558 self.handle.glyph_h_kerning(first_glyph, second_glyph)
559 }
560
561 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
562 {
563 let cache = self.cached_shape_data.read();
564 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
565 return *width;
566 }
567 }
568
569 let new_width = self
570 .handle
571 .glyph_h_advance(glyph_id)
572 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
573 let mut cache = self.cached_shape_data.write();
574 cache.glyph_advances.insert(glyph_id, new_width);
575 new_width
576 }
577
578 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
579 self.handle.typographic_bounds(glyph_id)
580 }
581
582 pub fn baseline(&self) -> Option<FontBaseline> {
584 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
585 }
586
587 #[cfg(not(target_os = "macos"))]
588 pub(crate) fn find_fallback_using_system_font_api(
589 &self,
590 _: &FallbackFontSelectionOptions,
591 ) -> Option<FontRef> {
592 None
593 }
594}
595
596#[derive(Clone, Debug, MallocSizeOf)]
597pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
598
599impl PartialEq for FontRef {
600 fn eq(&self, other: &Self) -> bool {
601 Arc::ptr_eq(self, other)
602 }
603}
604
605impl Deref for FontRef {
606 type Target = Arc<Font>;
607 fn deref(&self) -> &Self::Target {
608 &self.0
609 }
610}
611
612#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
613pub struct FallbackKey {
614 script: Script,
615 unicode_block: Option<UnicodeBlock>,
616 language: Language,
617}
618
619impl FallbackKey {
620 fn new(options: &FallbackFontSelectionOptions) -> Self {
621 Self {
622 script: Script::from(options.character),
623 unicode_block: options.character.block(),
624 language: options.language,
625 }
626 }
627}
628
629#[derive(MallocSizeOf)]
633pub struct FontGroup {
634 descriptor: FontDescriptor,
637 families: SmallVec<[FontGroupFamily; 8]>,
640 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
645}
646
647impl FontGroup {
648 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
649 let families: SmallVec<[FontGroupFamily; 8]> = style
650 .font_family
651 .families
652 .iter()
653 .map(FontGroupFamily::local_or_web)
654 .collect();
655
656 FontGroup {
657 descriptor,
658 families,
659 fallbacks: Default::default(),
660 }
661 }
662
663 pub fn find_by_codepoint(
668 &self,
669 font_context: &FontContext,
670 codepoint: char,
671 next_codepoint: Option<char>,
672 language: Language,
673 ) -> Option<FontRef> {
674 let codepoint = match codepoint {
678 '\t' => ' ',
679 _ => codepoint,
680 };
681
682 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, language);
683
684 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
685 options.character.is_ascii_lowercase();
686 let font_or_synthesized_small_caps = |font: FontRef| {
687 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
688 return font.synthesized_small_caps.clone();
689 }
690 Some(font)
691 };
692
693 let font_has_glyph_and_presentation = |font: &FontRef| {
694 match options.presentation_preference {
696 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
697 return false;
698 },
699 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
700 return false;
701 },
702 _ => {},
703 }
704 font.has_glyph_for(options.character)
705 };
706
707 let char_in_template =
708 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
709
710 if let Some(font) = self.find(
711 font_context,
712 &char_in_template,
713 &font_has_glyph_and_presentation,
714 ) {
715 return font_or_synthesized_small_caps(font);
716 }
717
718 let fallback_key = FallbackKey::new(&options);
719 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) &&
720 char_in_template(fallback.template.clone()) &&
721 font_has_glyph_and_presentation(fallback)
722 {
723 return font_or_synthesized_small_caps(fallback.clone());
724 }
725
726 if let Some(font) = self.find_fallback_using_system_font_list(
727 font_context,
728 options.clone(),
729 &char_in_template,
730 &font_has_glyph_and_presentation,
731 ) {
732 let fallback = font_or_synthesized_small_caps(font);
733 if let Some(fallback) = fallback.clone() {
734 self.fallbacks.write().insert(fallback_key, fallback);
735 }
736 return fallback;
737 }
738
739 let first_font = self.first(font_context);
740 if let Some(fallback) = first_font
741 .as_ref()
742 .and_then(|font| font.find_fallback_using_system_font_api(&options)) &&
743 font_has_glyph_and_presentation(&fallback)
744 {
745 return Some(fallback);
746 }
747
748 first_font
749 }
750
751 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
753 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
761 let font_predicate = |_: &FontRef| true;
762 self.find(font_context, &space_in_template, &font_predicate)
763 .or_else(|| {
764 self.find_fallback_using_system_font_list(
765 font_context,
766 FallbackFontSelectionOptions::default(),
767 &space_in_template,
768 &font_predicate,
769 )
770 })
771 }
772
773 fn find(
777 &self,
778 font_context: &FontContext,
779 template_predicate: &impl Fn(FontTemplateRef) -> bool,
780 font_predicate: &impl Fn(&FontRef) -> bool,
781 ) -> Option<FontRef> {
782 self.families
783 .iter()
784 .flat_map(|family| family.templates(font_context, &self.descriptor))
785 .find_map(|template| {
786 template.font_if_matches(
787 font_context,
788 &self.descriptor,
789 template_predicate,
790 font_predicate,
791 )
792 })
793 }
794
795 fn find_fallback_using_system_font_list(
801 &self,
802 font_context: &FontContext,
803 options: FallbackFontSelectionOptions,
804 template_predicate: &impl Fn(FontTemplateRef) -> bool,
805 font_predicate: &impl Fn(&FontRef) -> bool,
806 ) -> Option<FontRef> {
807 iter::once(FontFamilyDescriptor::default())
808 .chain(
809 fallback_font_families(options)
810 .into_iter()
811 .map(|family_name| {
812 let family = SingleFontFamily::FamilyName(FamilyName {
813 name: family_name.into(),
814 syntax: FontFamilyNameSyntax::Quoted,
815 });
816 FontFamilyDescriptor::new(family, FontSearchScope::Local)
817 }),
818 )
819 .find_map(|family_descriptor| {
820 FontGroupFamily::from(family_descriptor)
821 .templates(font_context, &self.descriptor)
822 .find_map(|template| {
823 template.font_if_matches(
824 font_context,
825 &self.descriptor,
826 template_predicate,
827 font_predicate,
828 )
829 })
830 })
831 }
832}
833
834#[derive(MallocSizeOf)]
841struct FontGroupFamilyTemplate {
842 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
843 template: FontTemplateRef,
844 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
845 font: OnceLock<Option<FontRef>>,
846}
847
848impl From<FontTemplateRef> for FontGroupFamilyTemplate {
849 fn from(template: FontTemplateRef) -> Self {
850 Self {
851 template,
852 font: Default::default(),
853 }
854 }
855}
856
857impl FontGroupFamilyTemplate {
858 fn font(
859 &self,
860 font_context: &FontContext,
861 font_descriptor: &FontDescriptor,
862 ) -> Option<FontRef> {
863 self.font
864 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
865 .clone()
866 }
867
868 fn font_if_matches(
869 &self,
870 font_context: &FontContext,
871 font_descriptor: &FontDescriptor,
872 template_predicate: &impl Fn(FontTemplateRef) -> bool,
873 font_predicate: &impl Fn(&FontRef) -> bool,
874 ) -> Option<FontRef> {
875 if !template_predicate(self.template.clone()) {
876 return None;
877 }
878 self.font(font_context, font_descriptor)
879 .filter(font_predicate)
880 }
881}
882
883#[derive(MallocSizeOf)]
888struct FontGroupFamily {
889 family_descriptor: FontFamilyDescriptor,
890 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
891}
892
893impl From<FontFamilyDescriptor> for FontGroupFamily {
894 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
895 Self {
896 family_descriptor,
897 members: Default::default(),
898 }
899 }
900}
901
902impl FontGroupFamily {
903 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
904 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
905 }
906
907 fn templates(
908 &self,
909 font_context: &FontContext,
910 font_descriptor: &FontDescriptor,
911 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
912 self.members
913 .get_or_init(|| {
914 font_context
915 .matching_templates(font_descriptor, &self.family_descriptor)
916 .into_iter()
917 .map(Into::into)
918 .collect()
919 })
920 .iter()
921 }
922}
923
924#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
926pub enum FontSearchScope {
927 Any,
929
930 Local,
932}
933
934#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
936pub struct FontFamilyDescriptor {
937 pub(crate) family: SingleFontFamily,
938 pub(crate) scope: FontSearchScope,
939}
940
941impl FontFamilyDescriptor {
942 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
943 FontFamilyDescriptor { family, scope }
944 }
945
946 fn default() -> FontFamilyDescriptor {
947 FontFamilyDescriptor {
948 family: SingleFontFamily::Generic(GenericFontFamily::None),
949 scope: FontSearchScope::Local,
950 }
951 }
952}
953
954pub struct FontBaseline {
955 pub ideographic_baseline: f32,
956 pub alphabetic_baseline: f32,
957 pub hanging_baseline: f32,
958}
959
960#[cfg(all(
976 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
977 not(target_env = "ohos")
978))]
979pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
980 if value < mapping[0].0 {
981 return mapping[0].1;
982 }
983
984 for window in mapping.windows(2) {
985 let (font_config_value_a, css_value_a) = window[0];
986 let (font_config_value_b, css_value_b) = window[1];
987
988 if value >= font_config_value_a && value <= font_config_value_b {
989 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
990 return css_value_a + ((css_value_b - css_value_a) * ratio);
991 }
992 }
993
994 mapping[mapping.len() - 1].1
995}