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