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, XLang};
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, GlyphStore, LocalFontIdentifier, ShapedGlyph, 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<GlyphStore>>,
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: 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<GlyphStore> {
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(self, 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) -> GlyphStore {
473 let mut glyph_store = GlyphStore::new(text, 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 = advance_for_shaped_glyph(
482 Au::from_f64_px(self.glyph_h_advance(glyph_id)),
483 character,
484 options,
485 );
486 let offset = prev_glyph_id.map(|prev| {
487 let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
488 advance += h_kerning;
489 Point2D::new(h_kerning, Au::zero())
490 });
491
492 glyph_store.add_glyph(
493 character,
494 &ShapedGlyph {
495 glyph_id,
496 string_byte_offset,
497 advance,
498 offset,
499 },
500 );
501 prev_glyph_id = Some(glyph_id);
502 }
503 glyph_store
504 }
505
506 pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
507 let result = self.handle.table_for_tag(tag);
508 let status = if result.is_some() {
509 "Found"
510 } else {
511 "Didn't find"
512 };
513
514 debug!(
515 "{} font table[{}] in {:?},",
516 status,
517 str::from_utf8(tag.as_ref()).unwrap(),
518 self.identifier()
519 );
520 result
521 }
522
523 #[inline]
524 pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
525 {
526 let cache = self.cached_shape_data.read();
527 if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
528 return *glyph;
529 }
530 }
531 let codepoint = match self.descriptor.variant {
532 font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
533 font_variant_caps::T::Normal => codepoint,
534 };
535 let glyph_index = self.handle.glyph_index(codepoint);
536
537 let mut cache = self.cached_shape_data.write();
538 cache.glyph_indices.insert(codepoint, glyph_index);
539 glyph_index
540 }
541
542 pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
543 self.glyph_index(codepoint).is_some()
544 }
545
546 pub(crate) fn glyph_h_kerning(
547 &self,
548 first_glyph: GlyphId,
549 second_glyph: GlyphId,
550 ) -> FractionalPixel {
551 self.handle.glyph_h_kerning(first_glyph, second_glyph)
552 }
553
554 pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
555 {
556 let cache = self.cached_shape_data.read();
557 if let Some(width) = cache.glyph_advances.get(&glyph_id) {
558 return *width;
559 }
560 }
561
562 let new_width = self
563 .handle
564 .glyph_h_advance(glyph_id)
565 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
566 let mut cache = self.cached_shape_data.write();
567 cache.glyph_advances.insert(glyph_id, new_width);
568 new_width
569 }
570
571 pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
572 self.handle.typographic_bounds(glyph_id)
573 }
574
575 pub fn baseline(&self) -> Option<FontBaseline> {
577 self.shaper.get_or_init(|| Shaper::new(self)).baseline()
578 }
579
580 #[cfg(not(target_os = "macos"))]
581 pub(crate) fn find_fallback_using_system_font_api(
582 &self,
583 _: &FallbackFontSelectionOptions,
584 ) -> Option<FontRef> {
585 None
586 }
587}
588
589#[derive(Clone, Debug, MallocSizeOf)]
590pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
591
592impl Deref for FontRef {
593 type Target = Arc<Font>;
594 fn deref(&self) -> &Self::Target {
595 &self.0
596 }
597}
598
599#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
600pub struct FallbackKey {
601 script: Script,
602 unicode_block: Option<UnicodeBlock>,
603 lang: XLang,
604}
605
606impl FallbackKey {
607 fn new(options: &FallbackFontSelectionOptions) -> Self {
608 Self {
609 script: Script::from(options.character),
610 unicode_block: options.character.block(),
611 lang: options.lang.clone(),
612 }
613 }
614}
615
616#[derive(MallocSizeOf)]
620pub struct FontGroup {
621 descriptor: FontDescriptor,
624 families: SmallVec<[FontGroupFamily; 8]>,
627 fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
632}
633
634impl FontGroup {
635 pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
636 let families: SmallVec<[FontGroupFamily; 8]> = style
637 .font_family
638 .families
639 .iter()
640 .map(FontGroupFamily::local_or_web)
641 .collect();
642
643 FontGroup {
644 descriptor,
645 families,
646 fallbacks: Default::default(),
647 }
648 }
649
650 pub fn find_by_codepoint(
655 &self,
656 font_context: &FontContext,
657 codepoint: char,
658 next_codepoint: Option<char>,
659 lang: XLang,
660 ) -> Option<FontRef> {
661 let codepoint = match codepoint {
665 '\t' => ' ',
666 _ => codepoint,
667 };
668
669 let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
670
671 let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
672 options.character.is_ascii_lowercase();
673 let font_or_synthesized_small_caps = |font: FontRef| {
674 if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
675 return font.synthesized_small_caps.clone();
676 }
677 Some(font)
678 };
679
680 let font_has_glyph_and_presentation = |font: &FontRef| {
681 match options.presentation_preference {
683 EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
684 return false;
685 },
686 EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
687 return false;
688 },
689 _ => {},
690 }
691 font.has_glyph_for(options.character)
692 };
693
694 let char_in_template =
695 |template: FontTemplateRef| template.char_in_unicode_range(options.character);
696
697 if let Some(font) = self.find(
698 font_context,
699 &char_in_template,
700 &font_has_glyph_and_presentation,
701 ) {
702 return font_or_synthesized_small_caps(font);
703 }
704
705 let fallback_key = FallbackKey::new(&options);
706 if let Some(fallback) = self.fallbacks.read().get(&fallback_key) {
707 if char_in_template(fallback.template.clone()) &&
708 font_has_glyph_and_presentation(fallback)
709 {
710 return font_or_synthesized_small_caps(fallback.clone());
711 }
712 }
713
714 if let Some(font) = self.find_fallback_using_system_font_list(
715 font_context,
716 options.clone(),
717 &char_in_template,
718 &font_has_glyph_and_presentation,
719 ) {
720 let fallback = font_or_synthesized_small_caps(font);
721 if let Some(fallback) = fallback.clone() {
722 self.fallbacks.write().insert(fallback_key, fallback);
723 }
724 return fallback;
725 }
726
727 let first_font = self.first(font_context);
728 if let Some(fallback) = first_font
729 .as_ref()
730 .and_then(|font| font.find_fallback_using_system_font_api(&options))
731 {
732 if font_has_glyph_and_presentation(&fallback) {
733 return Some(fallback);
734 }
735 }
736
737 first_font
738 }
739
740 pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
742 let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
750 let font_predicate = |_: &FontRef| true;
751 self.find(font_context, &space_in_template, &font_predicate)
752 .or_else(|| {
753 self.find_fallback_using_system_font_list(
754 font_context,
755 FallbackFontSelectionOptions::default(),
756 &space_in_template,
757 &font_predicate,
758 )
759 })
760 }
761
762 fn find(
766 &self,
767 font_context: &FontContext,
768 template_predicate: &impl Fn(FontTemplateRef) -> bool,
769 font_predicate: &impl Fn(&FontRef) -> bool,
770 ) -> Option<FontRef> {
771 self.families
772 .iter()
773 .flat_map(|family| family.templates(font_context, &self.descriptor))
774 .find_map(|template| {
775 template.font_if_matches(
776 font_context,
777 &self.descriptor,
778 template_predicate,
779 font_predicate,
780 )
781 })
782 }
783
784 fn find_fallback_using_system_font_list(
790 &self,
791 font_context: &FontContext,
792 options: FallbackFontSelectionOptions,
793 template_predicate: &impl Fn(FontTemplateRef) -> bool,
794 font_predicate: &impl Fn(&FontRef) -> bool,
795 ) -> Option<FontRef> {
796 iter::once(FontFamilyDescriptor::default())
797 .chain(
798 fallback_font_families(options)
799 .into_iter()
800 .map(|family_name| {
801 let family = SingleFontFamily::FamilyName(FamilyName {
802 name: family_name.into(),
803 syntax: FontFamilyNameSyntax::Quoted,
804 });
805 FontFamilyDescriptor::new(family, FontSearchScope::Local)
806 }),
807 )
808 .find_map(|family_descriptor| {
809 FontGroupFamily::from(family_descriptor)
810 .templates(font_context, &self.descriptor)
811 .find_map(|template| {
812 template.font_if_matches(
813 font_context,
814 &self.descriptor,
815 template_predicate,
816 font_predicate,
817 )
818 })
819 })
820 }
821}
822
823#[derive(MallocSizeOf)]
830struct FontGroupFamilyTemplate {
831 #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
832 template: FontTemplateRef,
833 #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
834 font: OnceLock<Option<FontRef>>,
835}
836
837impl From<FontTemplateRef> for FontGroupFamilyTemplate {
838 fn from(template: FontTemplateRef) -> Self {
839 Self {
840 template,
841 font: Default::default(),
842 }
843 }
844}
845
846impl FontGroupFamilyTemplate {
847 fn font(
848 &self,
849 font_context: &FontContext,
850 font_descriptor: &FontDescriptor,
851 ) -> Option<FontRef> {
852 self.font
853 .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
854 .clone()
855 }
856
857 fn font_if_matches(
858 &self,
859 font_context: &FontContext,
860 font_descriptor: &FontDescriptor,
861 template_predicate: &impl Fn(FontTemplateRef) -> bool,
862 font_predicate: &impl Fn(&FontRef) -> bool,
863 ) -> Option<FontRef> {
864 if !template_predicate(self.template.clone()) {
865 return None;
866 }
867 self.font(font_context, font_descriptor)
868 .filter(font_predicate)
869 }
870}
871
872#[derive(MallocSizeOf)]
877struct FontGroupFamily {
878 family_descriptor: FontFamilyDescriptor,
879 members: OnceLock<Vec<FontGroupFamilyTemplate>>,
880}
881
882impl From<FontFamilyDescriptor> for FontGroupFamily {
883 fn from(family_descriptor: FontFamilyDescriptor) -> Self {
884 Self {
885 family_descriptor,
886 members: Default::default(),
887 }
888 }
889}
890
891impl FontGroupFamily {
892 fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
893 FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
894 }
895
896 fn templates(
897 &self,
898 font_context: &FontContext,
899 font_descriptor: &FontDescriptor,
900 ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
901 self.members
902 .get_or_init(|| {
903 font_context
904 .matching_templates(font_descriptor, &self.family_descriptor)
905 .into_iter()
906 .map(Into::into)
907 .collect()
908 })
909 .iter()
910 }
911}
912
913#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
915pub enum FontSearchScope {
916 Any,
918
919 Local,
921}
922
923#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
925pub struct FontFamilyDescriptor {
926 pub(crate) family: SingleFontFamily,
927 pub(crate) scope: FontSearchScope,
928}
929
930impl FontFamilyDescriptor {
931 pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
932 FontFamilyDescriptor { family, scope }
933 }
934
935 fn default() -> FontFamilyDescriptor {
936 FontFamilyDescriptor {
937 family: SingleFontFamily::Generic(GenericFontFamily::None),
938 scope: FontSearchScope::Local,
939 }
940 }
941}
942
943pub struct FontBaseline {
944 pub ideographic_baseline: f32,
945 pub alphabetic_baseline: f32,
946 pub hanging_baseline: f32,
947}
948
949#[cfg(all(
965 any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
966 not(target_env = "ohos")
967))]
968pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
969 if value < mapping[0].0 {
970 return mapping[0].1;
971 }
972
973 for window in mapping.windows(2) {
974 let (font_config_value_a, css_value_a) = window[0];
975 let (font_config_value_b, css_value_b) = window[1];
976
977 if value >= font_config_value_a && value <= font_config_value_b {
978 let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
979 return css_value_a + ((css_value_b - css_value_a) * ratio);
980 }
981 }
982
983 mapping[mapping.len() - 1].1
984}
985
986pub(super) fn advance_for_shaped_glyph(
988 mut advance: Au,
989 character: char,
990 options: &ShapingOptions,
991) -> Au {
992 if let Some(letter_spacing) = options.letter_spacing_for_character(character) {
993 advance += letter_spacing;
994 };
995
996 if character == ' ' || character == '\u{a0}' {
1001 advance += options.word_spacing;
1003 }
1004
1005 advance
1006}